【问题标题】:How to get IntPtr of the array within List<T>?如何在 List<T> 中获取数组的 IntPtr?
【发布时间】:2016-01-23 10:15:17
【问题描述】:

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

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

如何从List&lt;T&gt;(和/或T[])中存储的数组中获取IntPtr

【问题讨论】:

  • 调用 ToArray,还是对你来说太慢了?
  • @HansPassant:如果他在托管端创建数组,这有GC重新定位数组使地址无效的风险。
  • ToArray() 分配内存并进行复制,这是性能关键部分。该数组每 发送一次,而且很大。

标签: c# marshalling intptr


【解决方案1】:

如果这是对非托管代码的 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();
}

【讨论】:

  • 使用fixed 会更优雅:fixed(float* p = array) Send(p, array.length)
  • @firda:当然,但前提是你被允许在你的项目中使用不安全的代码
  • @taffer 为什么不允许他使用不安全的代码?它与固定句柄和 PInvoke 具有相同的安全含义。
  • 这看起来很棒,有没有机会为 List 提供类似的机制?据我所知, List 在内部使用了一个数组。
  • List&lt;T&gt;.ToArray()? ;)
【解决方案2】:

数组每帧发送一次,而且很大

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

或者,更好的是,编写您自己的列表,您可以控制并从中获取数组。 (既然您似乎关心性能,我想知道您为什么要使用 List&lt;float&gt;,因为与普通数组相比,访问项目要慢。)

获取数组,然后使用fixed(float* ptr = array) SendBuffer(ptr, length) 固定它并传递它而不复制内存。

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

【讨论】:

    【解决方案3】:

    不保证List&lt;T&gt; 的内部表示将是单个数组...实际上很可能不是。因此,您需要使用 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 的更多错误处理答案)。

    在这两种情况下,您都需要在返回之前完成该值,因此您不能使用任何一种方法将IntPtr 作为返回值传递给数组。这样做可以最大限度地减少该指针被用于作恶的机会。

    【讨论】:

    • GCHandle.Alloc 实际上不会创建副本。如果SomeFunction 操作缓冲区,它将反映在您的数组中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-08
    • 2013-08-23
    • 2017-03-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多