我们如何复制由 Marshal.AllocHGlobal 分配的两个缓冲区?

How can we copy two buffers both allocated by Marshal.AllocHGlobal?

Marshal 中有一些方法可以复制 AllocHGlobal to/from C# 数组分配的原始缓冲区。但我的问题是,我有两个缓冲区都由 AllocHGlobal 分配,我想简单地将一个缓冲区复制到另一个缓冲区。

var buffer1 = Marshal.AllocHGlobal(size);
var buffer2 = Marshal.AllocHGlobal(size);
nativeOpOn(buffer1);
SomeCopy(buffer1, buffer2, size);

我知道一种可能的解决方案是 P/Invoke 用于 memcpy 的系统 C 库,但是我必须编写不同的 P/Invoke 来支持 Windows 和 Linux 和 MacOS,因为它们可能有不同的 P/Invoke 内容。

您可以使用以下方法:

1) 托管缓冲区:

int size = sizeof(int); // for test purposes
IntPtr buffer1 = Marshal.AllocHGlobal(size);
IntPtr buffer2 = Marshal.AllocHGlobal(size);

Marshal.WriteInt32(buffer1, 0x12345678); // native op (for test purposes)

byte[] bytes = new byte[size]; // managed buffer
Marshal.Copy(buffer1, bytes, 0, size); //buffer1=>bytes
Marshal.Copy(bytes, 0, buffer2, size); //bytes=>buffer2
// use several iterations if needed

int res =  Marshal.ReadInt32(buffer2, 0); // for test purposes (res==0x12345678)

2) 通过 Marshal.ReadByte/WriteByte 方法或任何 Marshal.ReadXXXe/WriteXXX 方法(取决于 size)管理 read/write 操作。循环重复这些操作即可:

for(int i = 0; i < size; i++) 
    Marshal.WriteByte(buffer2, i, Marshal.ReadByte(buffer1, i));

3) 不安全块(read/write 通过不安全指针缓冲)

如果您使用的是 .Net 4.6 或更高版本,您可以使用 Buffer.MemoryCopy():

unsafe
{
    Buffer.MemoryCopy(p1.ToPointer(), p2.ToPointer(), destSizeBytes, sourceBytesToCopy);
}

当然是unsafe.

如果您是 运行 .NET Core 或 .NET 5+,那么使用 Unsafe.CopyBlock()/Unsafe.CopyBlockUnaligned() 方法是最快的。

运行 以下基准使用 BenchmarkDotNet

// .NET 6.0.300

[MemoryDiagnoser]
[RPlotExporter]
public unsafe class CopyTests
{
    private readonly void* source;
    private readonly void* destination;
    
    private const int SIZE = 1024;
    
    public CopyTests()
    {
        source = NativeMemory.Alloc(SIZE);
        destination = NativeMemory.Alloc(SIZE);
    }

    [Benchmark]
    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
    public void TestUnsafeCopyBlockUnaligned()
    {
        Unsafe.CopyBlockUnaligned(destination, source, SIZE);
    }

    [Benchmark]
    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
    public void TestBufferMemoryCopy()
    {
        Buffer.MemoryCopy(source, destination, SIZE, SIZE);
    }
}

产生以下结果:

|                       Method |     Mean |    Error |   StdDev | Allocated |
|----------------------------- |---------:|---------:|---------:|----------:|
| TestUnsafeCopyBlockUnaligned | 16.63 ns | 0.350 ns | 0.360 ns |         - |
|         TestBufferMemoryCopy | 23.88 ns | 0.264 ns | 0.234 ns |         - |

或者如果您喜欢漂亮的图表:

如您所见,Unsafe.CopyBlockUnaligned() 快了大约 25%。

如果您正在使用 IntPtr,您显然必须先调用 myIntPtr.ToPointer() 并在您的项目设置中允许不安全的块:)