传递 wchar_t 字符串 from/to 本机插件 - 仅适用于 Windows?

Pass wchar_t strings from/to native plugin - only works on Windows?

我基本上想做的是将一个 string 对象传递给本机插件,并返回一个 string 对象(不一定是 from/to 相同的函数,但这并不毕竟没关系)。

经过相当多的工作后,我已经确定了这样的事情(简化示例):

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 下尝试相同的代码,这似乎无法正常工作。为了简化,我将测试函数缩短为仅将 return L"Test" 复制到输出缓冲区(上例中的 result )。但是,在 Unity 中,我只收到一个字符串 "T"

总的来说,这对我来说似乎是一些奇怪的编码问题,但我无法确定。我错过了什么吗?

更新: 我已经能够确定问题所在,这确实是一个编码问题。 Mono 将总是 假定wchar_t 的宽度为2 个字节(在Windows 上),但这将在Unix 和MacOS 上中断,因为它们使用wchar_t,也就是4个字节宽。 寻找不涉及太多开销的简洁解决方案。

发布问题后,我花了好几个小时尝试根据 codecvt header 结合 std::wstring_convert 实施正确的(和动态的)转换。虽然这很好用,但它有一个主要缺陷:截至撰写本文时,std::wstring_convert 似乎在所有(否则 C++11 兼容)编译器中都缺失,除了 Visual Studio 和 Clang(使用 libc++ ).所以这可能是一个符合标准的解决方案,但如果它几乎不可能使用并且仍然需要你跳篮球(我个人放弃尝试在 Debian 下的这个设置中使用 cross-compile 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;
}

当然还有优化的空间,根据实际用例,可能有一些更好的方法来实现无效重新分配等。

尽管如此,您可以在自己的项目中随意使用它,但请记住我可能在某处遗漏了一些重要的点,尽管到目前为止 运行 对我来说还不错。