2009-05-18 45 views
5

我即將開始閱讀噸的二進制文件,每個文件有1000或更多的記錄。新文件不斷添加,因此我正在編寫一個Windows服務來監視目錄並在收到新文件時處理這些文件。這些文件是用C++程序創建的。我已經在c#中重新創建了結構定義,並且可以讀取數據,但是我擔心我這樣做的方式最終會殺死我的應用程序。將C++結構體編組爲C#的最有效方法是什麼?

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

我不認爲我需要使用的GCHandle因爲我沒有真正與C++應用程序通信,一切都被從託管代碼完成的,但我不知道的替代方法的。

回答

6

對於您的特定應用程序,只有一件事會給你明確的答案:配置文件。

這裏說的是我在使用大型PInvoke解決方案時學到的教訓。編組數據的最有效方法是封送易接收的字段。這意味着CLR可以簡單地完成相當於memcpy的任務,以在本機代碼和託管代碼之間移動數據。簡而言之,從結構中獲取所有非內聯數組和字符串。如果它們存在於本地結構中,則用IntPtr表示它們,並將這些值按需編組到代碼託管中。

我還沒有分析過使用Marshal.PtrToStructure與具有本地API解引用值之間的區別。如果PtrToStructure通過分析顯示爲瓶頸,那麼這可能是您應該投資的東西。

對於大型層次結構按需編組,而不是一次將整個結構拖入托管代碼。在處理大型樹結構時,我遇到了這個問題。對單個節點進行編組速度非常快,如果它的靈活性和性能明智,那麼只需編組當前需要的內容即可。

7

使用Marshal.PtrToStructure比較慢。我發現在CodeProject下面的文章被比較(和基準)的讀取二進制數據不同的方式非常有幫助:

Fast Binary File Reading with C#

+1

謝謝,這articlt不僅顯示了文件處理程序之間的差異,但也給字節到結構轉換的一個很好的例子。 – 2012-05-10 13:11:50

1

這可能是你的問題的範圍之外,但我會傾向於在Managed C++中編寫一個fread()或類似的快速讀取結構的程序集。一旦你將它們讀入,你就可以使用C#來完成你需要的一切。

2

除了JaredPar的全面解答,您不需要使用GCHandle,您可以改用不安全的代碼。

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

GCHandle/fixed聲明的全部目的是引腳/修復特定的內存段,使得內存但從GC的點不動。如果內存是可移動的,任何重定位都會使你的指針無效。

不知道哪種方式更快。

+0

感謝您的建議。我要像Jarred建議的那樣配置文件,但我也會使用這種方法進行配置。 – scottm 2009-05-18 15:07:39

0

這裏是我在玩結構化文件的同時回來的一個小課程。這是我在不安全的時候能夠想出的最快速的方法(這正是我試圖取代並保持可比較的性能的原因。)

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 

使用:

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

(美麗新來的,希望沒有太多張貼......剛剛粘貼在班上,沒有砍出來的意見或任何東西以縮短它。)

0

看來這與C++和編組無關。你知道結構還有什麼你需要的。

顯然,你需要一個簡單的代碼,將讀取表示一個結構字節的組,然後使用BitConverter放置字節到相應的C#領域..

相關問題