通过JNI从C++传递双字节(WCHAR)字符串到Java

8
我有一个Java应用程序,通过JNI使用C++ DLL。其中一些DLL方法需要字符串参数,并且其中一些方法返回包含字符串的对象。
目前,该DLL不支持Unicode,因此字符串处理相当容易:
- Java调用String.getBytes()并将生成的数组传递给DLL,DLL只是将数据视为char*。 - DLL使用NewStringUTF()从const char*创建jstring。
我现在正在修改DLL以支持Unicode,转而使用TCHAR类型(当定义了UNICODE时使用Windows的WCHAR数据类型)。修改DLL进展顺利,但我不确定如何修改代码的JNI部分。
我现在唯一能想到的就是:
- Java调用String.getBytes(String charsetName)并将生成的数组传递给DLL,DLL将数据视为wchar_t*。 - DLL不再创建Strings,而是传递带有原始字符串数据的jbyteArrays。Java使用String(byte[] bytes,String charsetName)构造函数实际创建String。
这种方法唯一的问题是我不确定要使用哪个字符集名称。 WCHARs的长度为2字节,因此我非常确定它是UTF-16,但在Java端有3个可能性。 UTF-16,UTF-16BE和UTF-16LE。 我没有找到任何告诉我字节顺序的文档,但我可以通过快速测试来解决它。
是否有更好的方法?如果可能,我希望在DLL内继续构造jstring对象,因为这样我就不必修改那些方法的任何用法。 但是,NewString JNI方法不会采用charset标识符。
2个回答

7

这个答案表明WCHARS的字节序不能保证...

由于你在Windows上,你可以尝试使用WideCharToMultiByte将WCHARs转换为UTF-8,然后使用现有的JNI代码。

使用WideCharToMultiByte时需要小心,因为lpMultiByteStr参数可能会发生缓冲区溢出。为了避免这种情况,你应该调用两次函数,首先将 lpMultiByteStr 设置为 NULL ,将 cbMultiByte 设置为零 - 这将返回所需的 lpMultiByteStr 缓冲区的长度,而不尝试写入它。一旦你获得了长度,你就可以分配所需大小的缓冲区并再次调用函数。

示例代码:

int utf8_length;

wchar_t* utf16 = ...;

utf8_length = WideCharToMultiByte(
  CP_UTF8,           // Convert to UTF-8
  0,                 // No special character conversions required 
                     // (UTF-16 and UTF-8 support the same characters)
  utf16,             // UTF-16 string to convert
  -1,                // utf16 is NULL terminated (if not, use length)
  NULL,              // Determining correct output buffer size
  0,                 // Determining correct output buffer size
  NULL,              // Must be NULL for CP_UTF8
  NULL);             // Must be NULL for CP_UTF8

if (utf8_length == 0) {
  // Error - call GetLastError for details
}

char* utf8 = ...; // Allocate space for UTF-8 string

utf8_length = WideCharToMultiByte(
  CP_UTF8,           // Convert to UTF-8
  0,                 // No special character conversions required 
                     // (UTF-16 and UTF-8 support the same characters)
  utf16,             // UTF-16 string to convert
  -1,                // utf16 is NULL terminated (if not, use length)
  utf8,              // UTF-8 output buffer
  utf8_length,       // UTF-8 output buffer size
  NULL,              // Must be NULL for CP_UTF8
  NULL);             // Must be NULL for CP_UTF8

if (utf8_length == 0) {
  // Error - call GetLastError for details
}

在查找其他内容时发现了这个。是的,Unicode 在磁盘上可能具有不同的顺序,但如果您将内存中的 Unicode 字符串或 BSTR 转换为内存中的 jstring,则为什么 Java 不使用系统的本机字节顺序?这个问题有几个答案表明它是相同的。 - Rup
在Java和JNI中使用UTF-8时必须小心,因为它们使用的是一种修改版的UTF-8,该版本对空字符和补充字符的处理方式与标准的UTF-8不同。 - Remy Lebeau
@RemyLebeau 我很想看一下这方面的参考资料,你有吗? - Matthew Murdoch
java.sun.com 上的文档链接目前无法正常工作,但是这里有一个从Yahoo缓存的副本。页面大约在2/3的位置有一个名为“Modified UTF-8”的部分:http://74.6.117.48/search/srpcache?ei=UTF-8&p=java+utf-8+utf-16&fr=yfp-t-701&u=http://cc.bingj.com/cache.aspx?q=java+utf-8+utf-16&d=4937207144120700&mkt=en-US&setlang=en-US&w=868d5b7e,ab064145&icp=1&.intl=us&sig=.fgCA3B8u8vjq8Wxe6Hjbg--。 - Remy Lebeau
在Oracle的网站上找到了有关JNI使用Modified UTF-8的参考资料:http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp16542 - Remy Lebeau
显示剩余3条评论

2
我找到了一个关于字节顺序标记的小型常见问题解答。同时在该常见问题解答中提到:
UTF-16和UTF-32使用两个和四个字节长的代码单元。对于这些编码,有三种子编码:BE、LE和未标记的编码。BE形式使用大端字节序列化(最高有效字节在前),LE形式使用小端字节序列化(最低有效字节在前),而未标记的形式默认使用大端字节序列化,但可能在开头包含一个字节顺序标记以指示实际使用的字节序列化方式。
我假设在Java方面,UTF-16将尝试查找此BOM并正确处理编码。我们都知道假设是多么危险的事情...
因为评论的原因进行编辑:
Microsoft使用UTF16小端编码。Java UTF-16会尝试解释BOM。当缺少BOM时,它默认为UTF-16BE。BE和LE变体忽略BOM。

哦,我知道不同的UTF-16版本是什么,只是不知道Windows实际上用的是哪个WCHAR。 - Herms

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接