从PInvoke返回一个字符串?

我正在使用PInvoke实现本机代码(C ++)和托管代码(C#)之间的互操作性。 我只写了一个简单的函数,它从c ++代码中获取一个字符串。 我的代码看起来像C#代码:

[DllImport("MyDll.dll")] private static extern string GetSomeText(); public static string GetAllValidProjects() { string s = GetSomeText(); return s; } 

C ++代码

 char* GetSomeText() { std::string stri= "Some Text Here"; char * pchr = (char *)stri.c_str(); return pchr; } 

所有在c ++结束时工作正常,即pchr在“C#Here”中包含“Some Text Here”,但在C#中,字符串s包含注释。 我不知道我做错了什么。 任何帮助,将不胜感激

首先,正如其他人所指出的那样,即使在尝试互操作之前,您的C ++也已被破坏。 您正在返回指向stri缓冲区的指针。 但是因为stri在函数返回后立即被销毁,所以返回值无效。

更重要的是,即使你修复了这个问题,你还需要做更多。 它将无法在C ++代码中分配内存,而这需要C#代码来解除分配。

有几个选项可以正确完成。

您的C#代码可以询问C ++代码字符串的长度。 然后创建一个C#StringBuilder并将其分配给适当的大小。 接下来,StringBuilder对象将传递给C ++代码,其默认编组将作为LPWSTR。 在这种方法中,C#代码分配字符串,并且您的C ++代码接收必须复制缓冲区的C字符串。

或者,您可以从C ++返回BSTR,它允许在本机C ++代码中进行分配,并在C#代码中进行释放。

BSTR方法可能就是我的方法。 它看起来像这样:

C ++

 #include  BSTR GetSomeText() { return ::SysAllocString(L"Greetings from the native world!"); } 

C#

 [DllImport(@"test.dll", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.BStr)] private static extern string GetSomeText(); 

更新

Hans Passant在评论中补充了几个有用的观察结果。 首先,大多数P / Invoke互操作是针对现有的无法更改的界面完成的,并且您无法选择首选的互操作接口方法。 看来情况并非如此,应该选择哪种方法?

选项1是在首次询问本机代码需要多少空间之后,在托管代码中分配缓冲区。 也许使用双方同意的固定大小的缓冲区就足够了。

选项1落下的地方是组装字符串时价格昂贵且你不想做两次(例如一次返回它的长度,再一次用于内容)。 这是选项2, BSTR发挥作用的地方。

汉斯指出了BSTR一个缺点,即它带有UTF-16有效载荷,但你的源数据可能很好,这是一个“麻烦”。

为了克服麻烦,您可以将从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; } 

这是最困难的,现在很容易添加其他包装器从LPWSTRstd::stringstd::wrstring等转换为BSTR

这是因为

 std::string stri= "Some Text Here"; 

是一个堆栈对象,它在GetSomeText()调用后被销毁。 在您返回的调用pchr指针无效之后。

您可能需要动态地为文本分配空间以便能够访问它。
将您的C ++函数定义更改为:

 char* GetSomeText() { std::string stri = "Some Text Here"; return strcpy(new char[stri.size()], stri.c_str()); } 

像上面的东西。 你明白了……

从C ++获取字符串的另一种方法。 如果您无法修改您的c ++ DLL。 您可以使用IntPtr而不是字符串声明DllImport。 调用该函数时,可以将Ptr编组回String。

 [DllImport("MyDll.dll")] private static extern IntPtr GetSomeText(); public static string GetAllValidProjects() { string s = Marshal.PtrToStringAnsi(GetSomeText()); return s; } 

注意:如前一篇文章所述。 “Some Here Here”在堆栈上分配,因此只要函数返回,堆栈就会无线连接。 因此,数据有可能被覆盖。 因此,您应在调用后立即使用Marshal.PtrToStringAnsi。 不要坚持IntPtr。

 char* GetSomeText() { std::string stri= "Some Text Here"; char * pchr = (char *)stri.c_str(); return pchr; } 

如果你声明字符串变量来存储它,GetSomeText()返回char的指针是不行的。

尝试

 [DllImport("MyDll.dll")] private static extern string GetSomeText(); public static string GetAllValidProjects() { string s = new string(GetSomeText()); return s; } 

有一个构造函数需要char *