首先,正如其他人所指出的那样,在尝试交互之前,你的 C++ 代码就已经有问题了。你正在返回一个指向
stri
的缓冲区的指针。但是因为
stri
在函数返回时被销毁,返回值无效。
更重要的是,即使你修复了这个问题,你还需要做更多的工作。在你的 C++ 代码中分配内存是行不通的,因为你需要让 C# 代码来释放它。
有几种正确的方法可以解决这个问题。
你的 C# 代码可以询问 C++ 代码字符串的长度。然后创建一个 C# StringBuilder 对象,并将其分配到适当的大小。接下来,将 StringBuilder 对象传递给 C++ 代码,其默认的 marshalling 是 LPWSTR。在这种方法中,C# 代码分配字符串,而你的 C++ 代码接收到一个 C 字符串,必须将其复制到缓冲区中。
另一种方法是从 C++ 返回 BSTR,允许在本地 C++ 代码中分配内存,并在 C# 代码中释放。
我会选择使用 BSTR 方法。代码如下:
#include <comutil.h>
BSTR GetSomeText()
{
return ::SysAllocString(L"Greetings from the native world!");
}
C#(读作C sharp)
[DllImport(@"test.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string GetSomeText()
更新
在评论区,Hans Passant提供了几个有用的观察。首先,大多数P/Invoke互操作都是针对一个无法更改且您无法选择首选互操作接口的现有接口进行的。看起来这里并非如此,那么应选择哪种方法?
选项1是在托管代码中分配缓冲区,在询问本地代码需要多少空间后再进行。也许使用双方都同意的固定大小缓冲区就足够了。
选项1失败的地方在于当组装字符串很昂贵而且您不想做两次时(例如一次返回其长度,一次获取其内容)。这就是选项2 BSTR
的作用所在。
Hans指出了BSTR
的一个缺点,即它携带着UTF-16有效负载,但您的源数据很可能是char*
,这是“有点麻烦”的问题。
为了克服这个问题,您可以这样封装从char*
转换为BSTR
:
BSTR ANSItoBSTR(char* input)
{
BSTR result = NULL;
int lenA = lstrlenA(input);
int lenW = ::MultiByteToWideChar(CP_ACP, 0, input, lenA, NULL, 0);
if (lenW > 0)
{
result = ::SysAllocStringLen(0, lenW);
::MultiByteToWideChar(CP_ACP, 0, input, lenA, result, lenW);
}
return result;
}
那个最难的已经解决了,现在很容易添加其他包装器来将
LPWSTR
、
std::string
、
std::wrstring
等转换为
BSTR
。