从 PInvoke 返回一个字符串? [复制]
Posted
技术标签:
【中文标题】从 PInvoke 返回一个字符串? [复制]【英文标题】:Returning a string from PInvoke? [duplicate] 【发布时间】:2011-03-14 12:01:44 【问题描述】:我正在使用 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
包含“Some Text Here”,但在 C# 中,字符串 s
包含注释。我不知道我做错了什么。任何帮助将不胜感激
【问题讨论】:
可能重复***.com/questions/162897/marshal-char-in-c 完全可以理解为什么您没有找到原件,并且标题没有重叠。这不应该被删除,因此其他人可以在以后的搜索中找到它(以及它现在链接到的原件)。 【参考方案1】:首先,正如其他人所指出的,您的 C++ 甚至在尝试互操作之前就已损坏。您正在返回一个指向stri
缓冲区的指针。但是因为stri
在函数一返回就被销毁了,所以返回值是无效的。
更重要的是,即使你解决了这个问题,你也需要做更多的事情。它无法在您的 C++ 代码中分配内存,您需要 C# 代码解除分配。
有几个选项可以做到这一点。
您的 C# 代码可以询问 C++ 代码字符串的长度。然后创建一个 C# StringBuilder 并将其分配给适当的大小。接下来,将 StringBuilder 对象传递给 C++ 代码,其默认编组为 LPWSTR。在这种方法中,C# 代码分配字符串,而您的 C++ 代码接收一个 C 字符串,它必须将缓冲区复制到该字符串。
或者,您可以从 C++ 返回一个 BSTR,它允许在本机 C++ 代码中分配和在 C# 代码中解除分配。
BSTR 方法可能就是我的做法。它看起来像这样:
C++
#include <comutil.h>
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 在 cmets 中添加了一些有用的观察结果。首先,大多数 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
。
【讨论】:
嘿,你没有被否决。您可能想提一下,这也是 C++ 中未定义的行为。 @Hans 谢谢。我已经更新了。我不仅没有被否决,其他答案之一也被投了赞成票! @Hans 出于兴趣,既然您对此的了解比我多得多,那么您将如何将字符串从本地返回到托管?您认为 BSTR 方法是否合理,还是有更好的方法? 您通常没有选择,因为您通常必须与现有代码进行互操作。但是明智的 C 方式(C++ 不适用)是将缓冲区作为参数传递给函数。当然还有缓冲区长度。这种方式永远不会出现内存管理问题。它在 C 中很有效,因为您只需传递一个局部变量。缓冲区溢出风险不是很好。 BSTR 在 .NET 互操作中没有错,但 char* 有点麻烦。 @Hans 谢谢。这无疑是不正常的情况,因为提供的代码永远无法工作,即使是纯原生的。关于 BSTR 和 C++ 的重点是 char* 是 ANSI 而 BSTR 是宽字符?【参考方案2】:因为
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());
类似上面的东西。你明白了……
【讨论】:
如果我们没有跨越本机/托管边界并且双方都使用相同的分配器/释放器,那就没问题了。但这里不是这样。 @David 感谢 -1。返回本地 std::string 的 c_str() 的结果是无效的。 cplusplus.com/reference/string/string/c_str 不,但你的方式也行不通。在单个 C++ 模块中会很好,但不能与 .net 互操作一起使用。 我不是 .net 专家。但它应该可以工作,你肯定会遇到内存管理问题,而且它不是一个“好”的解决方案,但我希望你会得到 C# 代码中的文本!希望@Jame 能尽快确认。 它不起作用,它是 XP 中无法诊断的内存泄漏,Vista 及更高版本中的崩溃。希望 OP 不使用 XP,否则他会确认它“有效”。 pinvoke marshaller 无法释放字符串,它不使用相同的 CRT。无论如何它都会尝试使用 CoTaskMemFree()。【参考方案3】:另一种从 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 Text Here”在堆栈上分配,因此一旦函数返回,堆栈就会取消连接。因此,数据可能会被覆盖。因此,您应在通话后立即使用 Marshal.PtrToStringAnsi。不要坚持 IntPtr。
char* GetSomeText()
std::string stri= "Some Text Here";
char * pchr = (char *)stri.c_str();
return pchr;
【讨论】:
请,请,请不要提倡在内存被释放后使用它。你使用的是托管语言,一旦你从这个函数返回,GC 就可以决定收集内存、移动东西等等,如果这发生在你的 Marshal 调用之前,你会读入垃圾并且你什么都没有可以解决。【参考方案4】:如果你声明字符串变量来存储它,GetSomeText() 正在返回 char 的指针将不起作用。
试试
[DllImport("MyDll.dll")]
private static extern string GetSomeText();
public static string GetAllValidProjects()
string s = new string(GetSomeText());
return s;
有一个构造函数接受 char*
【讨论】:
pinvoke marshaller 无法处理 C++ 对象。 内存泄漏(并且不起作用)。以上是关于从 PInvoke 返回一个字符串? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
将带有结构字段的结构从 c++ 返回到 c# pinvoke
C DLL 中的 PInvoke char* 在 C# 中作为字符串处理。空字符问题