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

Initializing a C# IntPtr to accept data from unmanaged C++ DLL?

我在非托管 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。

我做错了什么?!

Marshal.PtrToStringBSTR备注的第一行是

Call this method only on strings that were allocated with the unmanaged SysAllocString and SysAllocStringLen functions.

这可能是您崩溃的原因。

除此之外,您的 C++ 函数需要 BSTR*(实际上是指向字符串中数据第一个字符的指针的指针),但您向它传递了一个指向数据的指针。

记住 BSTR 有一个特殊的结构:它以 4 个字节的长度开始,然后是数据,然后是空值。指针指向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 (带有特定的首字母capacity) - 编组层将其转换为 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();