8

我有一个通过 JNI 使用 C++ DLL 的 Java 应用程序。一些 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 不再创建字符串,而是将 jbyteArrays 与原始字符串数据一起传递。Java 使用 String(byte[] bytes, String charsetName) 构造函数来实际创建字符串。

这种方法的唯一问题是我不确定要使用什么字符集名称。WCHAR 的长度为 2 字节,所以我很确定它是 UTF-16,但在 java 端有 3 个可能性。UTF-16、UTF-16BE 和 UTF-16LE。我还没有找到任何文档告诉我字节顺序是什么,但我可能可以通过一些快速测试来弄清楚。

有没有更好的办法?如果可能的话,我想继续在 DLL 中构造 jstring 对象,因为这样我就不必修改这些方法的任何用法。但是,NewString JNI 方法不采用字符集标识符。

4

2 回答 2

7

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

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

由于参数中缓冲区溢出的可能性,您需要小心使用WideCharToMultiBytelpMultiByteStr。为了解决这个问题,您应该调用该函数两次,首先lpMultiByteStr设置为NULLcbMultiByte设置为零 - 这将返回所需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
}
于 2009-05-15T19:35:54.617 回答
2

我发现了一个关于字节顺序标记的小问题。同样来自该常见问题解答:

UTF-16 和 UTF-32 分别使用两个和四个字节长的代码单元。对于这些 UTF,有三种子风味:BE、LE 和未标记。BE 形式使用 big-endian 字节序列化(最高有效字节优先),LE 形式使用 little-endian 字节序列化(最低有效字节优先),未标记形式默认使用 big-endian 字节序列化,但可能包含字节顺序在开头标记以指示使用的实际字节序列化。

我假设在 java 端,UTF-16 会尝试找到这个 BOM 并正确处理编码。我们都知道假设是多么危险……

由于评论而编辑:

微软使用 UTF16 little endian。Java UTF-16 尝试解释 BOM。缺少 BOM 时,默认为 UTF-16BE。BE 和 LE 变体忽略 BOM。

于 2009-05-15T19:37:08.623 回答