我有一个使用本机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
的文档
释放先前通过调用
CoTaskMemAlloc
或CoTaskMemRealloc
函数分配的任务内存块。
如果函数没有以这种方式分配字符串(例如,它使用new
或malloc
(,则内存不会释放,并且会泄漏。必须确保使用与分配内存缓冲区相同的机制释放内存缓冲区。
马歇尔不会神奇地知道你使用了什么分配策略。理想情况下,您的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
不适用于结构成员或结构内部的数组。SizeConst
和LPArray
仍然有效。
我建议您在具体示例中指定CharSet
。
首先,我建议您重新考虑最初使用C++的原因,以及是否可以用C#编写该代码。
其次,如果可能的话,请使用BSTR
和SAFEARRAY
,编组器可以在所有情况下完全处理这两种情况,包括嵌套数组和字符串数组。
如果这不可能,请始终尝试从C#中分配数组缓冲区,并将它们作为[Out]
参数传入,因为这样编组器就可以分配非托管缓冲区(如果是blitable,则将其固定(,以便C++写入。编组器将自动复制并释放(或取消锁定(缓冲区