Unicode 字符串不适用于 P/Invoke 中的 void
Unicode string not working with void in P/Invoke
这是一个非常奇怪的问题,我不知道是什么原因造成的...
在C++ DLL中,我有一个方法,为了演示这个方法非常简单,你只需要传入两个字符指针,一个是源,一个是目标。很简单,当您 运行 该方法时,所有发生的事情就是它将所有字符从一个字符串复制到另一个字符串中。
这是方法:
extern "C" __declspec(dllexport) void GetPersonName(wchar_t* destination, wchar_t* source) {
unsigned int i = 0;
for (i = 0; i < wcslen(source); i++) {
destination[i] = source[i];
}
}
并且,在 C# 中,我使用 DllImport
调用此方法。当我调用它时,我传入一个 StringBuilder 作为目标,然后只传入一个 Unicode 字符串作为源(如果我只是传入一个字符串,则不会发生此问题ASCII 字符串)。为了演示我加了两个汉字(这个)。
[DllImport("CPPDLLImport.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
static extern void GetPersonName(StringBuilder output, string str);
...
var str = new StringBuilder();
GetPersonName(str, "Hi这个");
而且,它绝对没有将我预期的内容放入 StringBuilder - 而是将其放入:"Hi这个摨玗öö".
起初,我假设我在 C++/C# 代码中犯了错误,因此,为了调试它,我决定 return "i"(在 C++ 方法中) ,这样我就可以检查 for
循环走了多远,并且出于某种原因,当我这样做时,它突然起作用了。
当我将 C++ 方法设置为 return 一个整数(并将其放入 C# 中的变量中,如 var i = GetPersonName...
)时,出于某种原因,它突然将正确的字符串(即"source" 是什么)进入 StringBuilder。
然后我对其进行了进一步测试,将其 return 设为 布尔值 而不是整数,并在末尾拍打 return true
- 并且, 再一次,它工作正常。
那么,只要 return 类型不是 void
,它就可以工作吗?
为什么我会看到这种行为?而且,我怎样才能做到即使方法设置为 "void",它也能给出正确的输出。
您未能在 C++ 代码中对目标字符串进行 null 终止。添加该空终止符,您的代码将得到改进。
size_t len = wcslen(source);
for (size_t i = 0; i < len; i++) {
destination[i] = source[i];
}
destination[len] = 0;
另请注意,此代码避免在循环的每次迭代中调用 wcslen
。当然,您同样可以使用标准库功能来执行 C 字符串复制,无需为此编写另一个实现。
您仍然需要为字符串生成器对象设置适当的容量,否则您 运行 有较长字符串缓冲区溢出的风险。而且看起来您可能有 cdecl 与 stdcall 调用约定不匹配。
这是一个非常奇怪的问题,我不知道是什么原因造成的...
在C++ DLL中,我有一个方法,为了演示这个方法非常简单,你只需要传入两个字符指针,一个是源,一个是目标。很简单,当您 运行 该方法时,所有发生的事情就是它将所有字符从一个字符串复制到另一个字符串中。
这是方法:
extern "C" __declspec(dllexport) void GetPersonName(wchar_t* destination, wchar_t* source) {
unsigned int i = 0;
for (i = 0; i < wcslen(source); i++) {
destination[i] = source[i];
}
}
并且,在 C# 中,我使用 DllImport
调用此方法。当我调用它时,我传入一个 StringBuilder 作为目标,然后只传入一个 Unicode 字符串作为源(如果我只是传入一个字符串,则不会发生此问题ASCII 字符串)。为了演示我加了两个汉字(这个)。
[DllImport("CPPDLLImport.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
static extern void GetPersonName(StringBuilder output, string str);
...
var str = new StringBuilder();
GetPersonName(str, "Hi这个");
而且,它绝对没有将我预期的内容放入 StringBuilder - 而是将其放入:"Hi这个摨玗öö".
起初,我假设我在 C++/C# 代码中犯了错误,因此,为了调试它,我决定 return "i"(在 C++ 方法中) ,这样我就可以检查 for
循环走了多远,并且出于某种原因,当我这样做时,它突然起作用了。
当我将 C++ 方法设置为 return 一个整数(并将其放入 C# 中的变量中,如 var i = GetPersonName...
)时,出于某种原因,它突然将正确的字符串(即"source" 是什么)进入 StringBuilder。
然后我对其进行了进一步测试,将其 return 设为 布尔值 而不是整数,并在末尾拍打 return true
- 并且, 再一次,它工作正常。
那么,只要 return 类型不是 void
,它就可以工作吗?
为什么我会看到这种行为?而且,我怎样才能做到即使方法设置为 "void",它也能给出正确的输出。
您未能在 C++ 代码中对目标字符串进行 null 终止。添加该空终止符,您的代码将得到改进。
size_t len = wcslen(source);
for (size_t i = 0; i < len; i++) {
destination[i] = source[i];
}
destination[len] = 0;
另请注意,此代码避免在循环的每次迭代中调用 wcslen
。当然,您同样可以使用标准库功能来执行 C 字符串复制,无需为此编写另一个实现。
您仍然需要为字符串生成器对象设置适当的容量,否则您 运行 有较长字符串缓冲区溢出的风险。而且看起来您可能有 cdecl 与 stdcall 调用约定不匹配。