我在非托管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。
我在做什么错?!
答案 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();