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

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

本文关键字:C++ DLL 数据 IntPtr 初始化      更新时间:2023-10-16

我在非托管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备注的第一行是

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

这可能就是你的崩溃的来源。

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

请记住,BSTR 具有特殊的结构:它以 4 个字节的长度开始,然后是数据,然后是 null。指针指向数据的第一个字符。因此,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();