用于P /调用访问的固定数据

问题描述

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

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

我查阅了一些Microsoft文章,但对我而言,关于何时需要手动完成固定并不清楚。我了解this文章的方式是CLR确保GC收集时不会出现任何问题。它可以通过固定数据或将其复制到GC无法收集的非托管内存中来实现。因此,对我而言,这意味着程序员无需担心固定问题。 article中的示例也未显示任何固定。我仍然不确定我的结论是否正确。

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

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

IntPtr:我使用AllocHGlobal()在非托管内存中分配空间。 GC不会碰它。因此无需固定。

byte[]:通过引用但已自动固定。请参见here作为一种优化,在编组过程中,固定而不是复制的仅包含blittable成员的blittable类型和类的数组。

string:通过引用但已自动固定。请参见here在编组诸如String之类的对象时会自动执行钉扎,但是您也可以使用GCHandle类手动钉扎内存。

string[]'custom struct with strings':我真的不确定。句子“ 固定为字符串[...] 之类的对象时会自动执行钉扎”包括字符串数组和自定义结构吗?

目前,我什么都没有固定,代码也可以正常工作。即使当我强制GC在C ++执行任务时进行收集时。但是,当然这并不意味着它将始终正常运行。 我使用.Net Framework 4.8。

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

解决方法

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

如果您通过P / Invoke调用的方法在该P / Invoke调用期间仅使用指针 ,那么:在几乎所有情况下,您都可以接受并传递隐式指针,或在调用周围使用fixed(即,如果P / Invoke签名仅声明了SomeStruct*IntPtr),就可以正常工作。

如果您通过P / Invoke调用的方法在返回指针之前存储了指针,然后期望该指针有意义(可能是基于回调的异步/完成API),在这种情况下,您需要确保不会移动数据:

  • 对于非托管内存(例如来自AllocHGlobal的内存:没有必要
  • 用于内存降低堆栈(即在此期间不会重新使用堆栈帧):不需要
    • (如果您将堆栈存储器与P / Invoke一起使用,但不能满足该条件:您犯了一个严重的设计错误)
  • 用于托管内存(堆上的对象等);这就是乐趣的开始;请注意,.NET 5引入了“固定对象堆”(通常用于用于P / Invoke的数组),但除此之外:您需要手动固定