Что я в основном хочу сделать, это передать 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;
}
Бэкенд C # в сценарии Unity выглядит следующим образом:
[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"
только.
В целом, для меня это выглядит странной проблемой с кодировкой, но я не могу ее решить. Я что-то пропустил?
Обновить: Я был в состоянии идентифицировать проблему, и это действительно проблема кодирования. Моно будет всегда принять ширину wchar_t
составляет 2 байта (что в Windows), но это сломается в Unix и MacOS, так как они используют wchar_t
, что составляет 4 байта в ширину. Ищете аккуратное решение, которое не требует слишком много накладных расходов.
После публикации вопроса я потратил довольно много часов, пытаясь реализовать правильное (и динамическое) преобразование на основе codecvt
заголовок в сочетании с std::wstring_convert
, Хотя это прекрасно работало, у него был один существенный недостаток: на момент написания статьи std::wstring_convert
похоже, отсутствует во всех (иначе совместимых с C ++ 11) компиляторах, кроме Visual Studio и Clang (использующих libc ++). Так что это могло бы быть стандартным совместимым решением, но оно не очень практично, если его практически невозможно использовать, и все же требует от вас прыжков (лично я отказался от попытки кросс-компиляции кода x86 в этой настройке под Debian).
Так что вместо этого я закончил свою собственную и очень простую повторную реализацию преобразования. Под 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;
}
Конечно, есть место для оптимизации, и в зависимости от фактического варианта использования могут быть более эффективные способы сделать это с пустым перераспределением и т. Д.
Несмотря на это, не стесняйтесь использовать это в своих собственных проектах, но имейте в виду, что я мог где-то упустить какой-то важный момент, хотя пока он работает нормально для меня.
Других решений пока нет …