从/向本机插件传递 wchar_t 字符串 - 仅适用于 Windows?
Posted
技术标签:
【中文标题】从/向本机插件传递 wchar_t 字符串 - 仅适用于 Windows?【英文标题】:Pass wchar_t strings from/to native plugin - only works on Windows? 【发布时间】:2017-01-18 11:41:31 【问题描述】:我基本上想要做的是将string
对象传递给本机插件,并返回string
对象(不一定来自/到相同的函数,但这毕竟没关系)。
经过相当多的工作,我已经解决了这样的问题(简化示例):
EXPORT const wchar_t* myNativeFunction(const wchar_t* param)
// Allocate the memory for the return value (to be freed by the Mono Runtime)
#ifdef WIN32
wchar_t *result = static_cast<wchar_t *>(CoTaskMemAlloc((result_length + 1) * sizeof(wchar_t)));
#else
wchar_t *result = static_cast<wchar_t *>(malloc((result_length + 1) * sizeof(wchar_t)));
#endif
// Here I'd copy the actual results with a length of 'result_length'
return result;
Unity 脚本中的 C# 后端如下所示:
[DllImport(nativeLibrary, EntryPoint = "myNativeFunction", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static extern string myNativeFunction([MarshalAs(UnmanagedType.LPWStr)] string param);
在 Windows 下编译和运行时,它工作得很好。我得到了本地库的预期字符串,我得到了正确的字符串,无论那里是否有非 ANSI 字符。
但是,现在在 MacOS Sierra 下尝试相同的代码,这似乎无法正常工作。为简化起见,我缩短了一个测试函数,只返回复制到输出缓冲区的L"Test"
(上例中的result
)。但是,在 Unity 中,我只收到一个字符串 "T"
。
总的来说,这对我来说似乎是一些奇怪的编码问题,但我无法确定。我错过了什么吗?
更新:我已经能够确定问题所在,这确实是编码问题。 Mono 总是假设wchar_t
的宽度是 2 个字节(在 Windows 上是这样),但这在 Unix 和 MacOS 上会中断,因为它们使用的是 4 个字节宽的 wchar_t
。 寻找不涉及太多开销的简洁解决方案。
【问题讨论】:
在 MacOS 上你从"Test"
得到 T
的事实让我认为在那个平台上使用的 Unicode 格式是 UTF-8(而不是 Windows 上的 UTF-16),所以UTF-16 编码的T
的\0
字节被解释为UTF-8 格式的字符串结尾NUL
-终止符,因此字符串在T
处被截断。您可能想进一步研究 MacOS 上使用的 Unicode 格式(我对该平台的了解几乎为零)。 HTH。
@Mr.C64 是的,这正是我的想法,我自己也是一个 MacOS 菜鸟。 :D
【参考方案1】:
发布问题后,我花了好几个小时尝试基于codecvt
标头和std::wstring_convert
实现正确(和动态)转换。虽然这很好用,但它有一个主要缺陷:截至撰写本文时,除了 Visual Studio 和 Clang(使用 libc++)之外,所有(否则符合 C++11 的)编译器似乎都缺少std::wstring_convert
。所以这可能是一个符合标准的解决方案,但如果它几乎不可能使用并且仍然需要你跳圈(我个人放弃尝试在 Debian 下的这个设置中交叉编译 x86 代码),它就不太实用。
所以我最终得到的是我自己的非常简单的转换重新实现。在 Windows 下它不会做任何事情,只是直接传递字符串。使用起来也比std::wstring_convert
方便很多。
#if WIN32 // wchar_t *is* UTF-16
#define allocate(x) CoTaskMemAlloc(x)
std::wstring convert(const char16_t* text)
return reinterpret_cast<const wchar_t*>(text);
std::u16string convert(const wchar_t* text)
return reinterpret_cast<const char16_t*>(text);
#else
#define allocate(x) malloc(x)
std::wstring convert(const char16_t* text)
std::wstring ret;
const std::size_t maxlen = std::char_traits<char16_t>::length(text);
if (maxlen == 0)
return ret;
ret.reserve(maxlen);
for (char16_t s = *text; s; s = *(++text))
if (s < 0xd800)
ret += s;
else
wchar_t v = (s - 0xd800) * 0x400;
s = *(++text);
v += (s - 0xdc00) + 0x10000;
ret += v;
return ret;
std::u16string convert(const wchar_t* text)
std::u16string ret;
const std::size_t minlen = std::char_traits<wchar_t>::length(text);
if (minlen == 0)
return ret;
ret.reserve(minlen);
for (wchar_t v = *text; v; v = *(++text))
if (v < 0x10000)
ret += v;
else
ret += (v - 0x10000) / 0x400 + 0xd800;
ret += (v - 0x10000) % 0x400 + 0xdc00;
return ret;
#endif
用法相当简单:
void StringFromCSharp(const char16_t* text)
const std::wstring wtext(convert(text));
SomeWideCharStuff(wtext.c_str());
const char16_t* StringToCSharp()
const std::wstring(SomeWideCharReturningStuff());
const std::u16string converted(convert(buffer));
char16_t *result = static_cast<char16_t*>(allocate((converted.length() + 1) * sizeof(char16_t)));
memcpy(result, converted.c_str(), (converted.length() + 1) * sizeof(char16_t));
return result;
当然还有优化的空间,根据实际用例,可能有一些更好的方法可以对 void 重新分配等进行优化。
尽管如此,您可以在自己的项目中随意使用它,但请记住,我可能在某处遗漏了一些重要的点,尽管到目前为止它对我来说运行良好。
【讨论】:
以上是关于从/向本机插件传递 wchar_t 字符串 - 仅适用于 Windows?的主要内容,如果未能解决你的问题,请参考以下文章
C ++:从wchar_t *转换为unsigned short *是否安全?