Question

Je suis sur le point de commencer des tonnes de lecture de fichiers binaires, chacun avec 1000 ou plusieurs enregistrements. De nouveaux fichiers sont ajoutés en permanence, donc j'écris un service Windows pour surveiller les répertoires et traiter de nouveaux fichiers lors de leur réception. Les fichiers ont été créés avec un programme C ++. J'ai recréé les définitions de struct en C # et peut lire l'amende de données, mais je crains que la façon dont je le fais finira par tuer ma demande.

using (BinaryReader br = new BinaryReader(File.Open("myfile.bin", FileMode.Open)))
{
    long pos = 0L;
    long length = br.BaseStream.Length;

    CPP_STRUCT_DEF record;
    byte[] buffer = new byte[Marshal.SizeOf(typeof(CPP_STRUCT_DEF))];
    GCHandle pin;

    while (pos < length)
    {
        buffer = br.ReadBytes(buffer.Length);
        pin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        record = (CPP_STRUCT_DEF)Marshal.PtrToStructure(pin.AddrOfPinnedObject(), typeof(CPP_STRUCT_DEF));
        pin.Free();

        pos += buffer.Length;

        /* Do stuff with my record */
    }
}

Je ne pense pas que je dois utiliser GCHandle parce que je ne suis pas en train de communiquer avec l'application C ++, tout est fait à partir du code managé, mais je ne sais pas d'une autre méthode.

Était-ce utile?

La solution

Pour votre application, une seule chose vous donnera la réponse définitive: il Profil.

Cela étant dit, voici les leçons que je l'ai appris en travaillant avec de grandes solutions de PInvoke. Le moyen le plus efficace pour rassembler des données est de maréchal des champs qui sont blittable. Signification du CLR peut simplement faire ce qui équivaut à un memcpy pour transférer des données entre le code natif et géré. En termes simples, obtenir tous les tableaux non alignées et les chaînes de vos structures. Si elles sont présentes dans la structure native, les représenter avec un IntPtr et le maréchal des valeurs sur la demande en code managé.

Je n'ai jamais la différence entre profilée en utilisant Marshal.PtrToStructure vs ayant une API native déréférencer la valeur. Ceci est probablement quelque chose que vous devriez investir dans PtrToStructure devrait être révélé comme un goulot d'étranglement par le profilage.

Pour les grandes hiérarchies marshal à la demande par rapport à tirer une structure entière en code managé en une seule fois. J'ai rencontré ce problème le plus lorsqu'il s'agit de grandes structures d'arbres. Rassemblant un nœud individuel est très rapide si elle est blittable et la performance sage, il fonctionne au maréchal seulement ce dont vous avez besoin à ce moment-là.

Autres conseils

L'utilisation Marshal.PtrToStructure est plutôt lent. J'ai trouvé l'article suivant sur CodeProject qui compare (et analyse comparative) différentes façons de lire des données binaires très utiles:

  

fichier rapide binaire de lecture avec C #

En plus de réponse complète de JaredPar, vous n'avez pas besoin d'utiliser GCHandle, vous pouvez utiliser le code dangereux à la place.

fixed(byte *pBuffer = buffer) {
     record = *((CPP_STRUCT_DEF *)pBuffer);
}  

Le but de la GCHandle / déclaration de fixed est à la broche / fixer le segment de mémoire particulière, ce qui rend la mémoire immobile du point de vue de GC. Si la mémoire était mobile, tout déplacement rendrait vos pointeurs non valides.

Je ne sais pas de quelle manière est plus rapide cependant.

Cela peut être en dehors des limites de votre question, mais je serais enclin à écrire un petit assemblage managé C ++ qui a fait un fread () ou quelque chose de la même rapide à lire dans les struct. Une fois que vous les avez lues, vous pouvez utiliser C # pour faire tout ce que vous avez besoin avec eux.

Voici une petite classe j'ai fait un certain temps tout en jouant avec des fichiers structurés. il a été la méthode la plus rapide que je pouvais comprendre au moment d'aller timide dangereux (ce qui était ce que je cherchais à remplacer et maintenir des performances comparables.)

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;

namespace PersonalUse.IO {

    public sealed class RecordReader<T> : IDisposable, IEnumerable<T> where T : new() {

        const int DEFAULT_STREAM_BUFFER_SIZE = 2 << 16; // default stream buffer (64k)
        const int DEFAULT_RECORD_BUFFER_SIZE = 100; // default record buffer (100 records)

        readonly long _fileSize; // size of the underlying file
        readonly int _recordSize; // size of the record structure
        byte[] _buffer; // the buffer itself, [record buffer size] * _recordSize
        FileStream _fs;

        T[] _structBuffer;
        GCHandle _h; // handle/pinned pointer to _structBuffer 

        int _recordsInBuffer; // how many records are in the buffer
        int _bufferIndex; // the index of the current record in the buffer
        long _recordPosition; // position of the record in the file

        /// <overloads>Initializes a new instance of the <see cref="RecordReader{T}"/> class.</overloads>
        /// <summary>
        /// Initializes a new instance of the <see cref="RecordReader{T}"/> class.
        /// </summary>
        /// <param name="filename">filename to be read</param>
        public RecordReader(string filename) : this(filename, DEFAULT_STREAM_BUFFER_SIZE, DEFAULT_RECORD_BUFFER_SIZE) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="RecordReader{T}"/> class.
        /// </summary>
        /// <param name="filename">filename to be read</param>
        /// <param name="streamBufferSize">buffer size for the underlying <see cref="FileStream"/>, in bytes.</param>
        public RecordReader(string filename, int streamBufferSize) : this(filename, streamBufferSize, DEFAULT_RECORD_BUFFER_SIZE) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="RecordReader{T}"/> class.
        /// </summary>
        /// <param name="filename">filename to be read</param>
        /// <param name="streamBufferSize">buffer size for the underlying <see cref="FileStream"/>, in bytes.</param>
        /// <param name="recordBufferSize">size of record buffer, in records.</param>
        public RecordReader(string filename, int streamBufferSize, int recordBufferSize) {
            _fileSize = new FileInfo(filename).Length;
            _recordSize = Marshal.SizeOf(typeof(T));
            _buffer = new byte[recordBufferSize * _recordSize];
            _fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None, streamBufferSize, FileOptions.SequentialScan);

            _structBuffer = new T[recordBufferSize];
            _h = GCHandle.Alloc(_structBuffer, GCHandleType.Pinned);

            FillBuffer();
        }

        // fill the buffer, reset position
        void FillBuffer() {
            int bytes = _fs.Read(_buffer, 0, _buffer.Length);
            Marshal.Copy(_buffer, 0, _h.AddrOfPinnedObject(), _buffer.Length);
            _recordsInBuffer = bytes / _recordSize;
            _bufferIndex = 0;
        }

        /// <summary>
        /// Read a record
        /// </summary>
        /// <returns>a record of type T</returns>
        public T Read() {
            if(_recordsInBuffer == 0)
                return new T(); //EOF
            if(_bufferIndex < _recordsInBuffer) {
                // update positional info
                _recordPosition++;
                return _structBuffer[_bufferIndex++];
            } else {
                // refill the buffer
                FillBuffer();
                return Read();
            }
        }

        /// <summary>
        /// Advances the record position without reading.
        /// </summary>
        public void Next() {
            if(_recordsInBuffer == 0)
                return; // EOF
            else if(_bufferIndex < _recordsInBuffer) {
                _bufferIndex++;
                _recordPosition++;
            } else {
                FillBuffer();
                Next();
            }
        }

        public long FileSize {
            get { return _fileSize; }
        }

        public long FilePosition {
            get { return _recordSize * _recordPosition; }
        }

        public long RecordSize {
            get { return _recordSize; }
        }

        public long RecordPosition {
            get { return _recordPosition; }
        }

        public bool EOF {
            get { return _recordsInBuffer == 0; }
        }

        public void Close() {
            Dispose(true);
        }

        void Dispose(bool disposing) {
            try {
                if(disposing && _fs != null) {
                    _fs.Close();
                }
            } finally {
                if(_fs != null) {
                    _fs = null;
                    _buffer = null;
                    _recordPosition = 0;
                    _bufferIndex = 0;
                    _recordsInBuffer = 0;
                }
                if(_h.IsAllocated) {
                    _h.Free();
                    _structBuffer = null;
                }
            }
        }

        #region IDisposable Members

        public void Dispose() {
            Dispose(true);
        }

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator() {
            while(_recordsInBuffer != 0) {
                yield return Read();
            }
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        #endregion

    } // end class

} // end namespace

à utiliser:

using(RecordReader<CPP_STRUCT_DEF> reader = new RecordReader<CPP_STRUCT_DEF>(path)) {
    foreach(CPP_STRUCT_DEF record in reader) {
        // do stuff
    }
}

(assez nouveau ici, espère que ce pas trop pour poster ... juste collé dans la classe, ne pas couper les commentaires ou quoi que ce soit pour le raccourcir.)

Il semble que cela n'a rien à voir ni avec C ++, ni marshalling. Vous connaissez la structure quoi d'autre avez-vous besoin.

Il est évident que vous avez besoin d'un code simple qui va lire un groupe d'octets représentant un struct, puis en utilisant BitConverter pour placer des octets dans les champs correspondants C # ..

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top