P/Invoke 访问的固定数据

Pin data for P/Invoke access

我正在使用 P/Invoke 将数据从 C# 代码传递到 C++ 代码,反之亦然。到目前为止效果很好。

最近我读了几篇文章(例如 this one)关于固定这些数据的必要性,因为 GC 可能会在 C++ 执行其任务时重新排列或删除它们。

我查阅了一些 Microsoft 文章,但对我来说,它们并不完全清楚何时需要手动完成固定。我知道 this article in the way that the CLR assures that no problems can arise when the GC collects. It does that by either pinning data or copying them into unmanaged memory where the GC does not collect. So for me this means that the programmer does not need to take care of pinning. The example in this article 也没有显示任何固定。我还是不确定我的结论是否正确。

进一步挖掘,我发现了一些关于我在代码中使用的特定数据类型的更多信息:

intlong:按值传递 - 因此不得固定。但是 ref int 呢?

IntPtr:我使用 AllocHGlobal() 在非托管内存中分配 space。 GC 不会触及它。所以不需要固定。

byte[]:通过引用传递但自动固定。请参阅 here作为优化,可 blittable 类型的数组和仅包含 blittable 成员的 classes 在编组期间被固定而不是复制。

string:通过引用传递但自动固定。请参阅 here在封送对象(例如 String)期间自动执行固定,但是您也可以使用 GCHandle class.

手动固定内存

string[],'custom struct with strings':这里我真的不确定。 “Pinning is automatically performed during marshaling for objects such as String ”这句话是否包括字符串数组和自定义结构?

目前我没有固定任何东西,代码工作正常。即使我强制 GC 在 C++ 执行其任务时进行收集。但这当然并不意味着它会一直正常工作。 我使用 .Net Framework 4.8。

我需要对上述数据类型进行固定吗?

此处的规则取决于您所调用的内容,因此在没有具体示例的情况下只能给出模糊的建议,但是:

如果您通过 P/Invoke 调用的方法仅在 P/Invoke 调用 期间使用指针 ,则:几乎所有在你没问题的情况下,传递隐式指针,或在调用周围使用 fixed(即,如果 P/Invoke 签名仅声明 SomeStruct*IntPtr),将正常工作。

如果您通过 P/Invoke 调用的方法在其 returns 之前存储了指针 ,然后期望该指针有意义(也许对于 callback-based async/completion API),这就是您需要确保数据不会被移动的问题场景:

  • 对于来自 AllocHGlobal 的非托管内存:没有必要
  • 为了内存在堆栈的下方(即stack-frame在此期间不会被重用的地方):没有必要
    • (如果你在 P/Invoke 中使用堆栈内存并且你不能满足那个条件:你犯了一个严重的设计错误)
  • 用于托管内存(堆上的对象等);这就是乐趣的开始;请注意,.NET 5 引入了“固定对象堆”(通常用于 P/Invoke 的数组),但除此之外:您需要手动固定