C# 从PInvoke返回字符串?

C# 从PInvoke返回字符串?,c#,c++,pinvoke,C#,C++,Pinvoke,我使用PInvoke实现本机代码(C++)和托管代码(C#)之间的互操作性。 我只写了一个简单的函数,它从C++代码中得到一个字符串。我的代码看起来像 C#代码: [DllImport("MyDll.dll")] private static extern string GetSomeText(); public static string GetAllValidProjects() { string s = GetSomeText(); return s; } C++代码 c

我使用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语言中,字符串<代码> S/< >包含了注释。我不知道我做错了什么。任何帮助都将被理解为

< P>首先,正如其他人指出的,在尝试互操作之前,你的C++已经被破坏了。您正在返回一个指向stri的缓冲区的指针。但由于函数返回时,
stri
会立即被销毁,因此返回值无效

更重要的是,即使你解决了这个问题,你也需要做更多。它不能在你的C++代码中分配内存,你需要C代码来释放。 有几个选项可以正确地完成它

<>你的C代码可以问C++代码字符串的长度。然后创建一个C#StringBuilder并将其分配到适当的大小。接下来,StrugBuudor对象传递给C++代码,其默认编组是LPWSTR。在这种方法中,C代码分配字符串,C++代码接收一个C字符串,它必须复制缓冲区。 P>可选的,您可以从C++中返回BSTR,它允许在本地C++代码中分配和C代码中的分配。 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;
}
C++

#include <comutil.h>
BSTR GetSomeText()
{
    return ::SysAllocString(L"Greetings from the native world!");
}
更新

char* GetSomeText() {
    std::string stri= "Some Text Here";
    char * pchr = (char *)stri.c_str();
    return pchr;
}
汉斯·帕桑在评论中补充了一些有用的意见。首先,大多数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
,这是因为

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());
}
类似上面的东西。您知道了…

GetSomeText()正在返回char的指针,如果您声明字符串变量来存储它,它将不起作用

试一试


有一个构造函数,使用了<强> char */Struts>/P>< P>另一种从C++获取字符串的方法。万一你不能修改你的C++ DLL。可以使用IntPtr而不是字符串声明DllImport。调用函数时,可以将Ptr封送回字符串

[DllImport("MyDll.dll")]
private static extern IntPtr GetSomeText();
public static string GetAllValidProjects()
{
    string s = Marshal.PtrToStringAnsi(GetSomeText());
    return s;
}
注:如前一篇文章所述。“此处有一些文本”在堆栈上分配,因此一旦函数返回,堆栈将解除锁定。因此,数据可能被覆盖。因此,您应该在调用后立即使用Marshal.PtrToStringAnsi。不要拘泥于IntPtr

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

可能的重复完全可以理解为什么你没有找到原文,标题没有重叠。这不应该被删除,这样其他人可以在将来的搜索中找到它(以及它现在链接到的原始链接)。如果我们没有跨越本机/托管边界,并且双方使用相同的分配器/解除定位器,这就可以了。但事实并非如此。@David感谢-1。返回本地std::string的c_str()结果是无效的。不,但你的方式也不管用。它将在一个单一的C++模块中很好,但是不能与.NET互操作。我不是.NET专家。但它应该可以工作,你肯定会有内存管理问题,这不是一个“好”的解决方案,但我希望你能得到C代码的文本!希望@Jame能很快证实这一点。它不起作用,这是XP中无法诊断的内存泄漏,Vista和更高版本中的崩溃。希望OP不使用XP,否则他会确认它“有效”。pinvoke marshaller无法释放字符串,它不使用相同的CRT。无论如何,它都会尝试使用CoTaskMemFree()。嘿,你没有被否决。你可能要提到,这也是C++中未定义的行为。我已经更新了。我不仅没有被否决,还有一个答案被否决了@出于兴趣,Hans,既然你比我知道的更多,你会如何将字符串从本机返回到托管?在您看来,BSTR方法是明智的,还是有更好的方法?您通常没有选择,因为您通常必须与现有代码进行互操作。但明智的C方式(C++不适用)是将缓冲区作为参数传递给函数。当然还有缓冲区的长度。这样就不会有内存管理问题。它在C语言中是有效的,因为您只需传递一个局部变量。缓冲区溢出风险不是很好。BSTR在.NET互操作中并没有错,但在char*@Hans方面有点麻烦,谢谢。这无疑不是正常的情况,因为所提供的代码永远无法工作,即使是纯本机代码。关于BSTR和C++的观点是