初始化C#IntPtr以接受来自非托管C ++ DLL的数据吗?

时间:2019-03-28 14:07:47

标签: c# c++ interop

我在非托管C ++代码中有一个导出函数,该函数需要一个指向BStr的指针,它将在其中写入一些文本数据(最大258字节)

extern "C" __declspec(dllexport)
int CppFunc(BSTR *data)
{ ... }

我希望该数据作为字符串。

这有效

[DllImport( ... CallingConvention = CallingConvention.Cdecl)]
public static extern int CppFunc([MarshalAs(UnmanagedType.BStr)] ref string data);

但是会造成内存泄漏。

我假设我应该做的是创建并传递一个IntPtr,然后将Bstr作为字符串编组,然后释放IntPtr:

IntPtr p = Marshal.AllocHGlobal(512);
CppFunction(p);
string data = Marshal.PtrToStringBSTR(p);
Marshal.FreeHGlobal(p) ;

问题是,使用该代码,我在调用Marshal.PtrToStringBSTR(p)时收到System.AccessViolationException。

我在做什么错?!

1 个答案:

答案 0 :(得分:2)

Marshal.PtrToStringBSTR的备注的第一行是

  

仅在使用非托管SysAllocString和SysAllocStringLen函数分配的字符串上调用此方法。

您的崩溃可能来自哪里?

此外,您的C ++函数期望BSTR*(实际上是指向字符串中数据第一个字符的指针的指针),但是您将其传递给数据的指针。

请记住,BSTR具有特殊的结构:它以4个字节的长度开始,然后是数据,然后是null。指针指向 data 的第一个字符。因此,Marshal.PtrToStringBSTR正在从指针向后看 以查找字符串的长度-但这不是Marshal.AllocHGlobal分配的内存。


您的C ++函数可能执行类似*data = ....AllocSysString();的操作-也就是说,它从不读取给定的字符串,而是将指针分配给它分配的字符串。

在这种情况下,您可能想要类似的东西:

[DllImport( ... CallingConvention = CallingConvention.Cdecl)]
public static extern int CppFunc(out IntPtr data);

...

CppFunc(out IntPtr p);
string data = Marshal.PtrToStringBSTR(p);
Marshal.FreeBSTR(p) ;

在这里,我们将指针传递给指针。 C ++函数将指针重新分配为指向BSTR中数据的第一个字符,然后使用它来反序列化BSTR,然后释放它(使用一种知道如何释放BSTR的方法)。


如果不是这种情况,则不清楚您的C ++函数为什么要使用BSTR*(而不是BSTR),以及它的作用。我认为我们需要先看到这一点,然后才能说出很多其他内容。

如果您的C ++函数改用BSTR(请记住BSTR本身就是一个指针),那么您应该使用StringBuilder(具有特定的初始容量) -编组层将其转换为C ++代码可以写入的指针,然后可以将StringBuilder转换为字符串。

[DllImport( ... CallingConvention = CallingConvention.Cdecl)]
public static extern int CppFunc([MarshalAs(UnmanagedType.BStr)] StringBuilder data);

...

var data = new StringBuilder(512); 
CppFunction(data);
string result = data.ToString();