如何获取List<T>中数组的IntPtr?

How to get IntPtr of the array within List<T>?

我正在使用一个函数 SendBuffer(int size, IntPtr pointer) 的库,其中 IntPtr 作为参数。

 var list = new List<float>{3, 2, 1};
 IntPtr ptr = list.getPointerToInternalArray();
 SendBuffer(ptr, list.Count);

如何从List<T>(and/orT[])中存储的数组中得到IntPtr

如果这是对非托管代码的 P/Invoke 调用,您应该检索缓冲区的固定地址(以防止 GC 重新定位缓冲区)并将其传递给方法:

// use an array as a buffer
float[] buffer = new float[]{3, 2, 1};

// pin it to a fixed address:
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
    // retrieve the address as a pointer and use it to call the native method
    SendBuffer(handle.AddrOfPinnedObject(), buffer.Length);
}
finally
{
    // free the handle so GC can collect the buffer again
    handle.Free();
}

不能保证 List<T> 的内部表示将是单个数组...事实上很可能不是。因此,您需要使用 ToArray 创建本地数组副本才能使其正常工作。

完成后,有几个选项。

首先你可以使用fixed关键字固定数组并获取指向它的指针:

T[] buffer = theList.ToArray();
unsafe 
{
    fixed (T* p = buffer)
    {
        IntPtr ptr = (IntPtr)p;
        SomeFunction(ptr);
    }
}

或者,您可以告诉垃圾收集器修复内存中的数据,直到您完成操作,如下所示:

GCHandle pinned = GCHandle.Alloc(buffer, GCHandleType.Pinned);
IntPtr ptr = pinned.AddrOfPinnedObject();

SomeFunction(ptr);

pinnedArray.Free();

(或查看 taffer 的答案,了解更多错误处理)。

在这两种情况下,您都需要在 returning 之前以值结束,因此您不能使用任何一种方法将 IntPtr 作为 return 值获取到数组.这样做可以最大限度地减少该指针被用于作恶的机会。

The array is sent every frame and it's big

在这种情况下,可能需要访问 List 使用的内部支持数组。面对未来的 .NET 版本,这是一个 hack 和脆弱的问题。也就是说.NET 使用了非常高的兼容性标准,他们可能不会更改这种核心类型中的字段名称。此外,出于性能原因,几乎可以保证 List 将始终为其项目使用单个后备数组。因此,尽管这是一种高风险技术,但在这里可能是必要的。

或者,更好的是,编写您自己的列表,您可以控制它并且可以从中获取数组。 (既然你似乎关心 perf 我想知道你为什么要使用 List<float> 因为访问项目比普通数组慢。)

获取数组,然后用fixed(float* ptr = array) SendBuffer(ptr, length)固定,不拷贝内存传递

这里不需要使用笨拙和缓慢的GCHandle类型。使用 fixed 固定使用 IL 功能来使其超快。应该接近零成本。