6 Stimmen

Was ist der effizienteste Weg, um C++-Strukturen zu C# marshal?

Ich bin dabei, tonnenweise Binärdateien zu lesen, jede mit 1000 oder mehr Datensätzen. Es kommen ständig neue Dateien hinzu, also schreibe ich einen Windows-Dienst, der die Verzeichnisse überwacht und neue Dateien verarbeitet, sobald sie eingehen. Die Dateien wurden mit einem C++-Programm erstellt. Ich habe die Strukturdefinitionen in c# neu erstellt und kann die Daten gut lesen, aber ich bin besorgt, dass die Art und Weise, wie ich es tue, schließlich meine Anwendung beenden wird.

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 */
    }
}

Ich glaube nicht, dass ich GCHandle verwenden muss, weil ich nicht tatsächlich mit der C++-Anwendung kommuniziere, alles wird von verwaltetem Code getan, aber ich weiß nicht, eine alternative Methode.

8voto

Dirk Vollmar Punkte 166522

Verwendung von Marshal.PtrToStructure ist ziemlich langsam. Ich fand den folgenden Artikel auf CodeProject, der verschiedene Möglichkeiten zum Lesen von Binärdaten vergleicht (und vergleicht), sehr hilfreich:

Schnelles Lesen binärer Dateien mit C#

6voto

JaredPar Punkte 699699

Für Ihre spezielle Anwendung gibt es nur eine Möglichkeit, die endgültige Antwort zu finden: Das Profil.

Dies sind die Lektionen, die ich bei der Arbeit mit großen PInvoke-Lösungen gelernt habe. Die effektivste Art, Daten zu marshalen, ist es, Felder zu marshalen, die blittable sind. Das heißt, die CLR kann einfach tun, was auf ein Memcpy hinausläuft, um Daten zwischen nativem und verwaltetem Code zu übertragen. Einfach ausgedrückt, entfernen Sie alle nicht-inline Arrays und Strings aus Ihren Strukturen. Wenn sie in der nativen Struktur vorhanden sind, stellen Sie sie mit einem IntPtr und Marshal die Werte bei Bedarf in verwalteten Code.

Ich habe noch nie den Unterschied zwischen der Verwendung von Marshal.PtrToStructure und der Dereferenzierung des Wertes durch eine native API untersucht. Dies ist wahrscheinlich etwas, das Sie in investieren sollten, sollte PtrToStructure als Engpass über Profiling aufgedeckt werden.

Für große Hierarchien marshal on demand vs. ziehen eine gesamte Struktur in verwaltetem Code zu einem einzigen Zeitpunkt. Ich habe in dieses Problem die meisten laufen, wenn der Umgang mit großen Baumstrukturen. Marshalling einen einzelnen Knoten ist sehr schnell, wenn es blittable und Leistung weise es funktioniert, um nur zu marschieren, was Sie in diesem Moment benötigen.

3voto

arul Punkte 13880

Zusätzlich zu JaredPars umfassender Antwort müssen Sie nicht GCHandle können Sie stattdessen unsicheren Code verwenden.

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

Der gesamte Zweck der GCHandle / fixed Anweisung ist es, ein bestimmtes Speichersegment zu fixieren, wodurch der Speicher aus Sicht von GC unbeweglich wird. Wäre der Speicher beweglich, würde jede Verschiebung Ihre Zeiger ungültig machen.

Ich bin mir allerdings nicht sicher, welcher Weg schneller ist.

1voto

Eric Pohl Punkte 2284

Dies mag den Rahmen Ihrer Frage sprengen, aber ich würde dazu neigen, eine kleine Assembly in Managed C++ zu schreiben, die ein fread() oder etwas ähnlich Schnelles zum Einlesen der Strukturen verwendet. Sobald Sie sie eingelesen haben, können Sie C# verwenden, um alles andere zu tun, was Sie mit ihnen brauchen.

0voto

Sean Newton Punkte 145

Hier ist eine kleine Klasse, die ich eine Weile zurück gemacht, während das Spielen mit strukturierten Dateien. es war die schnellste Methode, die ich herausfinden konnte, zu der Zeit schüchtern zu gehen unsicher (was war, was ich versuchte, zu ersetzen und vergleichbare Leistung zu erhalten.)

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

zu verwenden:

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

(ziemlich neu hier, ich hoffe, das war nicht zu viel zu posten... habe nur die Klasse eingefügt, habe die Kommentare nicht herausgeschnitten oder so, um sie zu kürzen)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X