初始化 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();
我在非托管 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();