【发布时间】:2011-07-15 13:56:26
【问题描述】:
我正在使用 Pinvoke 实现本机 (C++) 代码和托管 (C#) 代码之间的互操作性。我想要实现的是从本机代码中获取一些文本到我的托管代码中。为此,我尝试了很多事情,例如通过 ref 传递字符串/字符串生成器,使用 [IN] 和 [OUT],编组到 LPSTR,从函数返回字符串等,但在我的情况下没有任何作用。对于一些小代码的任何帮助将不胜感激。
【问题讨论】:
我正在使用 Pinvoke 实现本机 (C++) 代码和托管 (C#) 代码之间的互操作性。我想要实现的是从本机代码中获取一些文本到我的托管代码中。为此,我尝试了很多事情,例如通过 ref 传递字符串/字符串生成器,使用 [IN] 和 [OUT],编组到 LPSTR,从函数返回字符串等,但在我的情况下没有任何作用。对于一些小代码的任何帮助将不胜感激。
【问题讨论】:
我会用 BSTR 来做这件事,因为这意味着您不必为每个字符串调用原生两次,一次获取长度,然后一次获取内容。
使用 BSTR,编组器将负责使用正确的内存管理器解除分配 BSTR,以便您可以安全地将其从 C++ 代码中传递出去。
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();
BSTR 有一个小缺点,即它携带 UTF-16 有效负载,但您的源数据很可能是 char*。
要克服这个问题,您可以像这样结束从 char* 到 BSTR 的转换:
BSTR ANSItoBSTR(const 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::wstring 等转换为BSTR。
【讨论】:
-1作为第四个参数传递给MultiByteToWideChar,让函数自动计算字符串的长度;避免需要自己打电话给lstrlenA。
char* 没有说明字符串的编码;如果是 UTF-8 编码的字符串,请考虑使用 CP_UTF8 而不是 CP_ACP。
【讨论】:
这是通过 C# 执行此操作的示例。我通过 pInvoking 通过 C# 调用 Native 函数 GetWindowText。 GetWindowText 返回 handle 传递给它的窗口的标题。
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetWindowTextLength(IntPtr hWnd);
public static string GetText(IntPtr hWnd)
{
// Allocate correct string length first
int length = GetWindowTextLength(hWnd);
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(hWnd, sb, sb.Capacity);
return sb.ToString();
}
private void button1_Click(object sender, EventArgs e)
{
string str = GetText(this.Handle);
}
【讨论】:
如果您要返回 char *,并且不想修改 C/C++ 代码来为您的返回值分配内存(或者您无法修改该代码),那么您可以更改您的C# extern function-prototype 返回一个IntPtr,然后自己进行封送处理。
例如,这是我为 Pocketsphinx 编写的互操作的 sn-p:
[DllImport("sphinxbase.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr /* char const * */ jsgf_grammar_name(IntPtr /* jsgf_t * */ jsgf);
这里是 C# JsgfGrammar 类的 get-accessor。 m_pU 是一个 IntPtr,它指向原始的 jsgf_t 对象。
public string Name
{
get
{
IntPtr pU = jsgf_grammar_name(m_pU);
if (pU == IntPtr.Zero)
strName = null;
else
strName = Marshal.PtrToStringAnsi(pU);
return strName;
}
}
为其他字符串格式(例如 Unicode)修改此示例应该很简单。
【讨论】:
为什么不构建自己的文本结构:
struct mystr { mystr(const char *_) : _text((_ == 0)
? 0
: ::strdup(_)
)
{}
~mystr() { if(this->_text != 0) delete [] this->_text; }
operator char * & () { return this->_text; }
private : char *_text;
};
【讨论】: