从PInvoke返回一个字符串?

时间:2011-03-14 12:01:45

标签: 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 ++代码

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

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

4 个答案:

答案 0 :(得分:15)

首先,正如其他人所指出的,即使在尝试互操作之前,您的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在评论中补充了几个有用的观察结果。首先,大多数P / Invoke互操作是针对现有的无法更改的界面完成的,并且您无法选择首选的互操作接口方法。看来情况并非如此,应选择哪种方法?

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

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

汉斯指出了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;
}

这是最困难的一个,现在很容易添加其他包装器以从BSTRLPWSTRstd::string等转换为std::wrstring

答案 1 :(得分:0)

这是因为

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());
}

像上面这样的东西。你明白了......

答案 2 :(得分:-1)

从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;
}

注意:如前一篇文章所述。 &#34;一些文字在这里&#34;在堆栈上分配,因此只要函数返回,堆栈就会解除连接。因此,数据可能被覆盖。因此,您应在调用后立即使用Marshal.PtrToStringAnsi。不要坚持使用IntPtr。

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

答案 3 :(得分:-3)

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

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

有一个带 char *

的构造函数