【问题标题】:What's the most efficient way to marshal C++ structs to C#?将 C++ 结构编组为 C# 的最有效方法是什么?
【发布时间】:2009-05-18 14:35:56
【问题描述】:

我即将开始阅读大量的二进制文件,每个文件都有 1000 条或更多记录。新文件不断添加,因此我正在编写一个 Windows 服务来监视目录并在收到新文件时对其进行处理。这些文件是使用 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++ 应用程序通信,一切都是通过托管代码完成的,但我不知道其他方法。

【问题讨论】:

    标签: c# performance pinvoke marshalling


    【解决方案1】:

    使用Marshal.PtrToStructure 相当慢。我发现以下关于 CodeProject 的文章比较(和基准测试)读取二进制数据的不同方式非常有帮助:

    Fast Binary File Reading with C#

    【讨论】:

    • 谢谢,这篇文章不仅展示了文件处理程序之间的差异,还给出了字节到结构转换的一个很好的例子。
    【解决方案2】:

    对于您的特定应用程序,只有一件事可以为您提供明确的答案:Profile it。

    这里说的是我在使用大型 PInvoke 解决方案时学到的经验教训。编组数据的最有效方法是编组可 blittable 的字段。这意味着 CLR 可以简单地执行相当于 memcpy 的操作,在本机代码和托管代码之间移动数据。简单来说,就是从你的结构中取出所有的非内联数组和字符串。如果它们存在于本机结构中,则使用 IntPtr 表示它们并根据需要将值编组到托管代码中。

    我从未分析过使用 Marshal.PtrToStructure 与使用本机 API 取消引用该值之间的区别。如果通过分析将 PtrToStructure 显示为瓶颈,这可能是您应该投资的东西。

    对于大型层次结构,按需编组与一次将整个结构拉入托管代码。在处理大型树结构时,我最常遇到这个问题。编组单个节点的速度非常快,如果它是 blittable 并且性能明智的话,它可以只编组你当时需要的东西。

    【讨论】:

      【解决方案3】:

      除了JaredPar的综合回答,你不需要使用GCHandle,你可以使用不安全的代码来代替。

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

      GCHandle/fixed 语句的全部目的是固定/修复特定的内存段,使内存从 GC 的角度来看是不可移动的。如果内存是可移动的,那么任何重定位都会使您的指针无效。

      不确定哪种方式更快。

      【讨论】:

      • 感谢您的建议。我会像 Jarred 建议的那样进行分析,但我也会使用这种方法进行分析。
      • @scottm 所以...最快的是什么?
      • 抱歉,这个问题大约 10 年前问过,记不得了
      【解决方案4】:

      这可能超出了您的问题范围,但我倾向于在托管 C++ 中编写一个小程序集,该程序集执行 fread() 或类似的快速读取结构的程序。将它们读入后,您就可以使用 C# 来完成您需要的所有其他操作。

      【讨论】:

        【解决方案5】:

        这是我不久前在玩结构化文件时开设的一个小班。这是我在避免不安全时能想到的最快方法(这是我试图替换并保持可比性能的方法。)

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

        (这里很新,希望不要发布太多...只是粘贴在课堂上,没有剪掉 cmets 或任何缩短它的东西。)

        【讨论】:

          【解决方案6】:

          这似乎与 C++ 和编组无关。你知道结构你还需要什么。

          显然,您需要一个简单的代码来读取代表一个结构的字节组,然后使用 BitConverter 将字节放入相应的 C# 字段中..

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-09-28
            • 1970-01-01
            • 2014-06-27
            • 1970-01-01
            • 1970-01-01
            • 2011-09-05
            • 1970-01-01
            相关资源
            最近更新 更多