从/向本机插件传递 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?的主要内容,如果未能解决你的问题,请参考以下文章

从char/wchar_t到TCHAR

C ++:从wchar_t *转换为unsigned short *是否安全?

不推荐从字符串 const 转换。到 wchar_t*

从本机 c 代码 (JNI) 为 Java 中的回调函数传递多个参数

在 C# 中元帅 C++ wchar_t**

将值从 iOS 本机代码传递给 cordova