我使用PInvoke实现本机代码(C++)和托管代码(C#)之间的互操作性.我只写了一个简单的函数,它从C++代码中获取一个字符串.我的代码看起来像
C#代码:
[DllImport("MyDll.dll")]
private static extern string GetSomeText();
public static string GetAllValidProjects() {
string s = GetSomeText();
return s;
}
Run Code Online (Sandbox Code Playgroud)
C++代码
char* GetSomeText() {
std::string stri= "Some Text Here";
char * pchr = (char *)stri.c_str();
return pchr;
}
Run Code Online (Sandbox Code Playgroud)
所有在C++端都可以正常工作,即变量pchr
包含"Some Text Here",但在C#中,字符串s
包含注释.我不知道我做错了什么.任何帮助,将不胜感激
Dav*_*nan 16
首先,正如其他人所指出的那样,即使在尝试互操作之前,您的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!");
}
Run Code Online (Sandbox Code Playgroud)
C#
[DllImport(@"test.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string GetSomeText();
Run Code Online (Sandbox Code Playgroud)
更新
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;
}
Run Code Online (Sandbox Code Playgroud)
这是方式的最难的了,现在可以很容易地添加其他包装转换到BSTR
从LPWSTR
,std::string
,std::wrstring
等.
归档时间: |
|
查看次数: |
11915 次 |
最近记录: |