2009-05-18 10 views
5

Ich bin im Begriff, Tonnen von Binärdateien mit jeweils 1000 oder mehr Datensätze zu lesen. Neue Dateien werden ständig hinzugefügt, daher schreibe ich einen Windows-Dienst, um die Verzeichnisse zu überwachen und neue Dateien zu verarbeiten, sobald sie empfangen werden. 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 umbringt.Was ist der effizienteste Weg, C++ - Strukturen zu C# zu marshallen?

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, ich brauche GCHandle zu verwenden, weil ich eigentlich nicht mit dem C++ App in Verbindung stehe, wird alles wird von verwalteten Code getan, aber ich weiß nicht, ein alternatives Verfahren.

Antwort

6

Für Ihre spezielle Anwendung gibt Ihnen nur eine Sache die definitive Antwort: Profilieren Sie sie.

Hier sind die Lektionen, die ich bei der Arbeit mit großen PInvoke-Lösungen gelernt habe. Der effektivste Weg, Daten zu marshalieren, besteht darin, Felder zu blättern, die blittierbar sind. Das heißt, die CLR kann einfach das tun, was einem Memcpy zum Verschieben von Daten zwischen nativem und verwaltetem Code entspricht. Vereinfachen Sie einfach alle Nicht-Inline-Arrays und Strings aus Ihren Strukturen. Wenn sie in der nativen Struktur vorhanden sind, stellen Sie sie mit einem IntPtr dar und ordnen Sie die Werte nach Bedarf in verwaltetem Code ein.

Ich habe noch nie den Unterschied zwischen der Verwendung von Marshal.PtrToStructure im Gegensatz zu einer nativen API dereferenziert den Wert. Dies ist wahrscheinlich etwas, in das Sie investieren sollten, sollte sich PtrToStructure durch Profiling als Flaschenhals entpuppen.

Für große Hierarchien Marshal on demand vs. Ziehen einer gesamten Struktur in verwalteten Code zu einem Zeitpunkt. Ich bin auf dieses Problem am meisten im Umgang mit großen Baumstrukturen gestoßen. Das Rangieren eines einzelnen Knotens ist sehr schnell, wenn es blittierbar ist, und leistungsfähig ist es, nur das zu managen, was Sie in diesem Moment benötigen.

7

Die Verwendung von Marshal.PtrToStructure ist eher langsam. Ich fand den folgenden Artikel auf Codeproject, die (und Benchmarking) verschiedene Arten des Lesens binäre Daten sehr hilfreich ist der Vergleich:

Fast Binary File Reading with C#

+1

Danke, das articlt zeigt nicht nur Unterschiede zwischen den Dateihandler, sondern gibt auch ein schönes Beispiel für die Konvertierung von Byte zu Struktur. –

1

Dies kann außerhalb der Grenzen Ihrer Frage, aber ich wäre geneigt, eine kleine Assembly in Managed C++ zu schreiben, die eine fread() oder etwas Ähnliches zum Einlesen der Strukturen verwendet. Sobald Sie sie eingelesen haben, können Sie mit C# alles andere tun, was Sie brauchen.

2

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

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

Der ganze Zweck der Erklärung GCHandle/fixed ist das besondere Speichersegment zum PIN/fixiert, wobei den Speicher von unbeweglichem GC-Sicht machen. Wenn der Speicher beweglich ist, würde jede Verschiebung Ihre Zeiger ungültig machen.

Nicht sicher, welcher Weg jedoch schneller ist.

+0

Danke für den Vorschlag. Ich werde ein Profil erstellen, wie es Jarred vorgeschlagen hat, aber ich werde auch mit dieser Methode profilieren. – scottm

0

Hier ist eine kleine Klasse, die ich beim Spielen mit strukturierten Dateien gemacht habe. es war die schnellste methode, die ich zu der zeit scheuen konnte, unsicher zu werden (was ich versuchte, vergleichbare leistung zu ersetzen und aufrechtzuerhalten).)

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 hoffen, das war in der Klasse nicht zu viel zu schreiben ... nur eingefügt, nicht die Kommentare oder etwas hacken um es zu verkürzen.)

0

Es scheint, dass dies weder mit C++ noch mit Marshalling etwas zu tun hat. Sie kennen die Struktur, was Sie sonst noch brauchen.

Offensichtlich benötigen Sie einen einfachen Code, der Gruppe von Bytes einer Struktur gelesen wird repräsentiert und dann BitConverter mit Bytes in entsprechende C# Felder zu platzieren ..