【问题标题】:Pin data for P/Invoke accessP/Invoke 访问的 Pin 数据
【发布时间】:2020-12-25 18:54:57
【问题描述】:

我正在使用 P/Invoke 将数据从 C#-Code 传递到 C++-Code,反之亦然。到目前为止,这工作正常。

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

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

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

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

IntPtr: 我使用 AllocHGlobal() 在非托管内存中分配空间。 GC 没有涉及到这一点。所以不需要固定。

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

string:通过引用传递但自动固定。请参阅here在编组对象(如字符串)期间自动执行固定,但是您也可以使用 GCHandle 类手动固定内存。

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

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

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

【问题讨论】:

  • 啊,博客文章。它在描述您需要在哪种情况下执行此操作时做得很差。这是非常少见的,一个需要它的 api 在非托管代码中与 pinvoke 一样难以使用和危险。仅当非托管代码存储您传递的指针时,才需要显式固定。你会期望一个 initialize 函数,一个或多个从你传递的数据中转换或提取信息的实用函数,以及(希望)一个 release 函数来表明你是不再调用实用程序函数。
  • 感谢您的快速回复。所以如果我只在函数调用期间使用传递的指针,那么我不需要固定它们?
  • 没错。

标签: c# c++ clr pinvoke


【解决方案1】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-04
    • 1970-01-01
    • 2011-10-15
    • 1970-01-01
    • 2013-05-31
    • 2015-11-17
    • 1970-01-01
    • 2013-06-11
    相关资源
    最近更新 更多