Есть ли простой способ преобразования строки Java в настоящий байтовый массив UTF-8 в коде JNI?
К сожалению, GetStringUTFChars() почти делает то, что требуется, но не совсем, он возвращает "модифицированную" последовательность байтов UTF-8. Основное отличие состоит в том, что модифицированный UTF-8 не содержит никаких нулевых символов (поэтому вы можете рассматривать строку ANSI C с нулевым завершением), но другая разница, похоже, заключается в том, как обрабатываются дополнительные символы Unicode, такие как emoji.
Символ, такой как U + 1F604 "СМОТРЕТЬ ЛИСТЬЮ С ОТКРЫТОМ РИТОМ И СМОТРЕТЬЮ ГЛАЗАМИ", хранится как суррогатная пара (два символа UTF-16 U + D83D U + DE04) и имеет 4-байтовый эквивалент UTF-8 F0 9F 98 84, и это байтовая последовательность, которую я получаю, если я преобразую строку в UTF-8 в Java:
char[] c = Character.toChars(0x1F604);
String s = new String(c);
System.out.println(s);
for (int i=0; i<c.length; ++i)
System.out.println("c["+i+"] = 0x"+Integer.toHexString(c[i]));
byte[] b = s.getBytes("UTF-8");
for (int i=0; i<b.length; ++i)
System.out.println("b["+i+"] = 0x"+Integer.toHexString(b[i] & 0xFF));
Приведенный выше код печатает следующее:
😄 c [0] = 0xd83d c [1] = 0xde04 b [0] = 0xf0 b [1] = 0x9f b [2] = 0x98 b [3] = 0x84
Однако, если я передаю 's' в собственный метод JNI и вызываю GetStringUTFChars(), я получаю 6 байтов. Каждый из символов суррогатной пары преобразуется в 3-байтную последовательность независимо:
JNIEXPORT void JNICALL Java_EmojiTest_nativeTest(JNIEnv *env, jclass cls, jstring _s)
{
const char* sBytes = env->GetStringUTFChars(_s, NULL);
for (int i=0; sBytes[i]!=0; ++i)
fprintf(stderr, "%d: %02x\n", i, sBytes[i]);
env->ReleaseStringUTFChars(_s, sBytes);
return result;
}
0: ed 1: a0 2: bd 3: ed 4: b8 5: 84
Статья Википедии UTF-8 предполагает, что GetStringUTFChars() фактически возвращает CESU-8, а не UTF-8. Это, в свою очередь, приводит к сбою моего родного кода Mac, поскольку это не действительная последовательность UTF-8:
CFStringRef str = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
CFURLRef url = CFURLCreateWithFileSystemPath(NULL, str, kCFURLPOSIXPathStyle, false);
Я полагаю, что я мог бы изменить все мои методы JNI, чтобы взять байт [], а не строку и сделать преобразование UTF-8 в Java, но это кажется немного уродливым, есть ли лучшее решение?