61 votes

Lire un fichier binaire dans une structure

J'essaie de lire des données binaires en utilisant C#. J'ai toutes les informations sur la disposition des données dans les fichiers que je veux lire. Je suis capable de lire les données "morceau par morceau", c'est-à-dire de récupérer les 40 premiers octets de données en les convertissant en une chaîne de caractères, puis de récupérer les 40 octets suivants.

Comme il existe au moins trois versions légèrement différentes des données, j'aimerais lire les données directement dans un struct. Cela me semble tellement plus juste que de les lire "ligne par ligne".

J'ai essayé l'approche suivante, mais sans succès :

StructType aStruct;
int count = Marshal.SizeOf(typeof(StructType));
byte[] readBuffer = new byte[count];
BinaryReader reader = new BinaryReader(stream);
readBuffer = reader.ReadBytes(count);
GCHandle handle = GCHandle.Alloc(readBuffer, GCHandleType.Pinned);
aStruct = (StructType) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(StructType));
handle.Free();

Le flux est un FileStream ouvert à partir duquel j'ai commencé à lire. J'obtiens un AccessViolationExceptio n lors de l'utilisation de Marshal.PtrToStructure .

Le flux contient plus d'informations que ce que j'essaie de lire puisque je ne suis pas intéressé par les données à la fin du fichier.

La structure est définie comme suit :

[StructLayout(LayoutKind.Explicit)]
struct StructType
{
    [FieldOffset(0)]
    public string FileDate;
    [FieldOffset(8)]
    public string FileTime;
    [FieldOffset(16)]
    public int Id1;
    [FieldOffset(20)]
    public string Id2;
}

Le code des exemples a été modifié par rapport à l'original pour rendre cette question plus courte.

Comment lire les données binaires d'un fichier dans une structure ?

36voto

Ishmaeel Points 7720

Le problème est le suivant chaîne de caractères dans votre structure. J'ai trouvé que le marshalage de types comme byte/short/int n'est pas un problème ; mais quand vous avez besoin de marshalage dans un type complexe comme une chaîne, vous avez besoin que votre structure imite explicitement un type non géré. Vous pouvez le faire avec l'attribut MarshalAs.

Pour votre exemple, ce qui suit devrait fonctionner :

[StructLayout(LayoutKind.Explicit)]
struct StructType
{
    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
    public string FileDate;

    [FieldOffset(8)]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
    public string FileTime;

    [FieldOffset(16)]
    public int Id1;

    [FieldOffset(20)]
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 66)] //Or however long Id2 is.
    public string Id2;
}

17voto

Voici ce que j'utilise.
Cela a fonctionné avec succès pour moi pour la lecture de Portable Executable Format.
Il s'agit d'une fonction générique, donc T est votre struct type.

public static T ByteToType<T>(BinaryReader reader)
{
    byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T)));

    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
    handle.Free();

    return theStructure;
}

7voto

nevelis Points 424

Comme l'a dit Ronnie, j'utiliserais BinaryReader et je lirais chaque champ individuellement. Je ne trouve pas le lien vers l'article contenant cette information, mais il a été observé que l'utilisation de BinaryReader pour lire chaque champ individuel peut être plus rapide que Marshal.PtrToStruct, si la structure contient moins de 30-40 champs environ. Je posterai le lien vers l'article dès que je le trouverai.

Le lien de l'article se trouve à l'adresse suivante : http://www.codeproject.com/Articles/10750/Fast-Binary-File-Reading-with-C

Lors de la mise à disposition d'un tableau de structs, PtrToStruct prend le dessus plus rapidement, car vous pouvez considérer le nombre de champs comme des champs * longueur du tableau.

2 votes

J'étais en train de lire : codeproject.com/KB/files/fastbinaryfileinput.aspx . Est-ce l'article auquel vous pensez ? L'auteur note : "J'ai constaté qu'à environ 40 champs, les résultats des trois approches étaient presque équivalents, et qu'au-delà, les approches de lecture en bloc prenaient le dessus."

3voto

Robert Höglund Points 5572

Je n'ai pas eu de chance en utilisant le BinaryFormatter, je suppose que je dois avoir une structure complète qui correspond exactement au contenu du fichier. J'ai réalisé qu'en fin de compte, je n'étais pas intéressé par une grande partie du contenu du fichier, donc j'ai opté pour la solution de lire une partie du flux dans un bytebuffer, puis de le convertir à l'aide de la fonction

Encoding.ASCII.GetString()

pour cordes et

BitConverter.ToInt32()

pour les entiers.

Je devrai être en mesure d'analyser une plus grande partie du fichier plus tard, mais pour cette version, je me suis contenté de quelques lignes de code.

3voto

lubos hasko Points 13669

Je ne vois pas de problème avec votre code.

Juste en dehors de ma tête, que se passe-t-il si vous essayez de le faire manuellement ? cela fonctionne-t-il ?

BinaryReader reader = new BinaryReader(stream);
StructType o = new StructType();
o.FileDate = Encoding.ASCII.GetString(reader.ReadBytes(8));
o.FileTime = Encoding.ASCII.GetString(reader.ReadBytes(8));
...
...
...

essayez aussi

StructType o = new StructType();
byte[] buffer = new byte[Marshal.SizeOf(typeof(StructType))];
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
Marshal.StructureToPtr(o, handle.AddrOfPinnedObject(), false);
handle.Free();

puis utiliser tampon[] dans votre BinaryReader au lieu de lire les données à partir de FileStream pour voir si vous obtenez toujours l'exception AccessViolation.

Je n'ai pas eu de chance en utilisant le BinaryFormatter, je suppose que je dois avoir une structure complète qui correspond le contenu du fichier exactement.

C'est logique, BinaryFormatter a son propre format de données, totalement incompatible avec le vôtre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X