P/Invoke访问的Pin数据



我使用p/Invoke将数据从C#代码传递到C++代码,反之亦然。到目前为止,这还可以。

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

我查阅了一些微软的文章,但对我来说,它们并不完全清楚何时需要手动进行固定。我理解这篇文章的方式是CLR确保GC收集时不会出现问题。它通过固定数据或将数据复制到GC不收集的非托管内存中来实现这一点。所以对我来说,这意味着程序员不需要负责固定。本文中的示例也没有显示任何固定。不过我仍然不确定我的结论是否正确。

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

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

IntPtr:我使用AllocHGlobal((,它在非托管内存中分配空间。GC没有触及这一点。因此不需要钉扎。

byte[]:通过引用传递,但自动固定。请参阅此处:作为优化,在封送处理期间,只包含blitable成员的blitable类型和类的数组将被固定,而不是复制

string:通过引用传递,但自动固定。请参阅此处:在封送处理String等对象时会自动执行固定,但您也可以使用GCHandle类手动固定内存

string[]'custom struct with strings':我真的不确定。句子">在封送处理诸如String[…]";包括字符串数组和自定义结构?

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

我需要为提到的数据类型进行固定吗?

这里的规则是以您调用的内容为条件的,因此只能在没有具体示例的情况下给出模糊的建议,但是:

如果您通过p/Invoke调用的方法在p/Invoke呼叫的持续时间内仅使用指针,那么:在几乎所有情况下,您都可以通过传递隐式指针,或在呼叫周围使用fixed(即,如果p/Invoke签名仅声明SomeStruct*IntPtr(,也可以正常工作。

如果您通过p/Invoke调用的方法在指针返回之前存储了该指针,然后期望该指针是有意义的(可能对于基于回调的异步/完成API(,则这就是您需要确保数据不会被移动的问题场景:

  • 对于来自AllocHGlobal的非托管内存:不需要任何东西
  • 对于堆栈下方的内存(即在此期间堆栈帧不会被重用的位置(:无需任何操作
    • (如果您使用P/Invoke的堆栈内存,但不能满足该条件:您犯了一个严重的设计错误(
  • 对于托管内存(堆上的对象等(;这就是乐趣的开始;注意,NET 5引入了一个";固定对象堆";(通常用于P/Invoke的数组(,但除此之外:您需要手动固定

最新更新