.Net CLR如何处理P/Invoke调用返回的内存



我有一个使用本机DLL的.Net 4.6 C#GUI应用程序。我想处理本机代码中的数据,并将本机端分配的内存返回到C#。

我已经阅读了StackOverflow上的各种问题和网络上的链接,但我想确保其中缺少一些内容,并可能将所有相关的互操作答案集中在一个地方。

情况1:

[DllImport("Native.dll")]
public static extern void GetStringA(
[MarshalAs(UnmanagedType.LPStr)]
out string str);
  • 我从以下文档中了解到,这一个将首先复制,并使用CoTaskMemFree释放原始缓冲区

https://learn.microsoft.com/en-us/dotnet/standard/native-interop/best-practices
Windows特定对于[Out]字符串,CLR默认情况下将使用CoTaskMemFree释放字符串,或使用SysStringFree释放标记为UnmanagedType.BSTR的字符串。

情况2:

[DllImport("Native.dll")]
public static extern void GetStringW(
[MarshalAs(UnmanagedType.LPWStr)]
out string str);
  • CLR将在str上调用CoTaskMemFree,还是将使用现有缓冲区并在以后的GC循环中调用CoTaskMemFree
    (请注意LPWStr(

情况3:

[DllImport("Native.dll")]
public static extern void GetStringA(
[MarshalAs(UnmanagedType.LPStr, SizeParamIndex = 1)]
out string str,
out int length);
  • CLR在回组时会考虑SizeParamIndex吗?还是只寻找null终止

情况4:

[DllImport("Native.dll")]
public static extern void GetInts(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
out int[] array,
out int length);
  • CLR将在array上调用CoTaskMemFree,还是将使用现有缓冲区并在以后的GC循环中调用CoTaskMemFree?文档讨论了字符串的CoTaskMem*函数,但我看不到它是否以同样的方式处理LPArray的信息
  • CLR在回组时会考虑SizeParamIndex吗?还是只对输入参数有效

情况5:

[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
[MarshalAs(UnmanagedType.LPStr)]
public string str;
[MarshalAs(UnmanagedType.LPArray)]
public int[] ints;
}
[DllImport("Native.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void GetStructs(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
out MyStruct[] array,
out int length);
  • 我能指望CLR像处理简单类型一样正确处理复杂类型吗
    它会以我自然期望的方式递归封送和复制/重用引用类型吗

TLDR;我如何处理内存管理&使用本机互操作时所有权是否完全正确?(仅供参考,我编写了本机DLL以及GUI应用程序。(

情况1:

您缺少的关键点是CoTaskMemFree的文档

释放先前通过调用CoTaskMemAllocCoTaskMemRealloc函数分配的任务内存块。

如果函数没有以这种方式分配字符串(例如,它使用newmalloc(,则内存不会释放,并且会泄漏。必须确保使用与分配内存缓冲区相同的机制释放内存缓冲区。

马歇尔不会神奇地知道你使用了什么分配策略。理想情况下,您的C++代码将提供FreeMemory函数。

情况2:

在指针被复制到out string之后,函数返回时立即调用CoTaskMemFree。一旦你的职能完成,编组员就不会介入。

情况3:

据我所知,SizeParamIndex只用于数组,而不用于字符串,因此将使用null终止。相反,您可以将其封送为byte[],然后将其复制出来。

[DllImport("Native.dll")]
public static extern void GetStringA(
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
out byte[] str,
out int length);

情况4:

不,马歇尔根本不会释放数组。你需要自己释放数组。

情况5:

是的,整理器将处理嵌套结构。但是有些事情是不能做的。例如,SizeParamIndex不适用于结构成员或结构内部的数组。SizeConstLPArray仍然有效。

我建议您在具体示例中指定CharSet


首先,我建议您重新考虑最初使用C++的原因,以及是否可以用C#编写该代码。

其次,如果可能的话,请使用BSTRSAFEARRAY,编组器可以在所有情况下完全处理这两种情况,包括嵌套数组和字符串数组。

如果这不可能,请始终尝试从C#中分配数组缓冲区,并将它们作为[Out]参数传入,因为这样编组器就可以分配非托管缓冲区(如果是blitable,则将其固定(,以便C++写入。编组器将自动复制并释放(或取消锁定(缓冲区

最新更新