JNA:仅更改一个外部本机库的字符串编码
JNA: Change String encoding for only one external native library
我们这里有一个 JAVA 应用程序,它加载和使用大量外部库。操作系统(Windows)的默认编码是“windows-1252”(或“cp-1252”)。但是有一个外部库需要“utf-8”中的所有字符串(传入和传出)。我怎样才能做到这一点?如何仅更改一个 JNA 库的字符串编码类型?
是针对此特定用例的更好解决方案。如果只需要字符编码请先阅读
我的原始答案在下面,并且对更广泛的应用程序更通用。
一个简单的处理方法是根本不直接映射 String
fields/args。只需从库中发送和接收字节数组,并创建一个辅助函数来完成字符串和字节数组之间的转换。正如您所指出的,您可以将这些字节写入分配的 Memory
块并传递指针。
如果你想要一个更永久的解决方案来在幕后做同样的事情,你可以为那个特定的库使用 TypeMapper。
W32APITypeMapper 是一个很好的参考,其中 stringConverter
变量向您展示了它如何在 unicode 中将 String
映射到宽字符串 WString
(UTF16)。
创建您自己的 UTF8TypeMapper
(或类似的)并使用 Java 的字符 set/encoding 函数将您的字符串转换为 UTF-8 字节序列。
这是未经测试的,但应该接近您的需要。您可以做更多的抽象来创建一个新的 UTF8String
类型来处理细节。
public class UTF8TypeMapper extends DefaultTypeMapper {
public UTF8TypeMapper() {
TypeConverter stringConverter = new TypeConverter() {
@Override
public Object toNative(Object value, ToNativeContext context) {
if (value == null)
return null;
String str = (String) value;
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
// Allocate an extra byte for null terminator
Memory m = new Memory(bytes.length + 1);
// write the string's bytes
m.write(0, bytes, 0, bytes.length);
// write the terminating null
m.setByte((long) bytes.length, (byte) 0);
return m;
}
@Override
public Object fromNative(Object value, FromNativeContext context) {
if (value == null)
return null;
Pointer p = (Pointer) value;
// handles the null terminator
return p.getString(0, StandardCharsets.UTF_8.name());
}
@Override
public Class<?> nativeType() {
return Pointer.class;
}
};
addTypeConverter(String.class, stringConverter);
}
}
然后,在加载库时将类型映射器添加到选项中:
private static final Map<String, ?> UTF8_OPTIONS =
Collections.singletonMap(Library.OPTION_TYPE_MAPPER, new UTF8TypeMapper());
TheUTF8Lib INSTANCE = Native.load("TheUTF8Lib", TheUTF8Lib.class, UTF8_OPTIONS);
我发现的唯一方法是像这样使用 JNA 的函数 class(参见 https://java-native-access.github.io/jna/5.2.0/javadoc/com/sun/jna/Function.html):
public void setIp(String ip) {
Function fSetIp = Function.getFunction("myLib", "setIp", Function.C_CONVENTION, "utf-8");
Object[] args = {ip};
fSetIp.invoke(args);
}
但是我必须为我想调用的每个函数实现它。不确定是否有 better/easier 方式。如果是:请回答我的问题。
正常的 JNA 模式是这样的:
public interface DemoLibrary extends Library {
DemoLibrary INSTANCE = Native.load("demoLibrary", DemoLibrary.class);
// abstract method declarations as interface to native library
}
但是,Native#load 被多次重载以支持自定义绑定。相关的重载是:Native#load(String, Class, Map<String,?>). The third argument can be used to pass options to the native library loader. The options can be found in the com.sun.jna.Library Interface.
这里的相关选项是Library.OPTION_STRING_ENCODING
。该选项将传递给加载的 NativeLibrary
实例,并将用作此 class.
的默认编码
上面的例子就变成了
public interface DemoLibrary extends Library {
DemoLibrary INSTANCE = Native.load("demoLibrary", DemoLibrary.class,
Collections.singletonMap(Library.OPTION_STRING_ENCODING, "UTF-8"));
}
如果您需要自定义更多(类型映射器、调用约定),则需要在静态初始化程序块中创建选项映射。
我们这里有一个 JAVA 应用程序,它加载和使用大量外部库。操作系统(Windows)的默认编码是“windows-1252”(或“cp-1252”)。但是有一个外部库需要“utf-8”中的所有字符串(传入和传出)。我怎样才能做到这一点?如何仅更改一个 JNA 库的字符串编码类型?
我的原始答案在下面,并且对更广泛的应用程序更通用。
一个简单的处理方法是根本不直接映射 String
fields/args。只需从库中发送和接收字节数组,并创建一个辅助函数来完成字符串和字节数组之间的转换。正如您所指出的,您可以将这些字节写入分配的 Memory
块并传递指针。
如果你想要一个更永久的解决方案来在幕后做同样的事情,你可以为那个特定的库使用 TypeMapper。
W32APITypeMapper 是一个很好的参考,其中 stringConverter
变量向您展示了它如何在 unicode 中将 String
映射到宽字符串 WString
(UTF16)。
创建您自己的 UTF8TypeMapper
(或类似的)并使用 Java 的字符 set/encoding 函数将您的字符串转换为 UTF-8 字节序列。
这是未经测试的,但应该接近您的需要。您可以做更多的抽象来创建一个新的 UTF8String
类型来处理细节。
public class UTF8TypeMapper extends DefaultTypeMapper {
public UTF8TypeMapper() {
TypeConverter stringConverter = new TypeConverter() {
@Override
public Object toNative(Object value, ToNativeContext context) {
if (value == null)
return null;
String str = (String) value;
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
// Allocate an extra byte for null terminator
Memory m = new Memory(bytes.length + 1);
// write the string's bytes
m.write(0, bytes, 0, bytes.length);
// write the terminating null
m.setByte((long) bytes.length, (byte) 0);
return m;
}
@Override
public Object fromNative(Object value, FromNativeContext context) {
if (value == null)
return null;
Pointer p = (Pointer) value;
// handles the null terminator
return p.getString(0, StandardCharsets.UTF_8.name());
}
@Override
public Class<?> nativeType() {
return Pointer.class;
}
};
addTypeConverter(String.class, stringConverter);
}
}
然后,在加载库时将类型映射器添加到选项中:
private static final Map<String, ?> UTF8_OPTIONS =
Collections.singletonMap(Library.OPTION_TYPE_MAPPER, new UTF8TypeMapper());
TheUTF8Lib INSTANCE = Native.load("TheUTF8Lib", TheUTF8Lib.class, UTF8_OPTIONS);
我发现的唯一方法是像这样使用 JNA 的函数 class(参见 https://java-native-access.github.io/jna/5.2.0/javadoc/com/sun/jna/Function.html):
public void setIp(String ip) {
Function fSetIp = Function.getFunction("myLib", "setIp", Function.C_CONVENTION, "utf-8");
Object[] args = {ip};
fSetIp.invoke(args);
}
但是我必须为我想调用的每个函数实现它。不确定是否有 better/easier 方式。如果是:请回答我的问题。
正常的 JNA 模式是这样的:
public interface DemoLibrary extends Library {
DemoLibrary INSTANCE = Native.load("demoLibrary", DemoLibrary.class);
// abstract method declarations as interface to native library
}
但是,Native#load 被多次重载以支持自定义绑定。相关的重载是:Native#load(String, Class, Map<String,?>). The third argument can be used to pass options to the native library loader. The options can be found in the com.sun.jna.Library Interface.
这里的相关选项是Library.OPTION_STRING_ENCODING
。该选项将传递给加载的 NativeLibrary
实例,并将用作此 class.
上面的例子就变成了
public interface DemoLibrary extends Library {
DemoLibrary INSTANCE = Native.load("demoLibrary", DemoLibrary.class,
Collections.singletonMap(Library.OPTION_STRING_ENCODING, "UTF-8"));
}
如果您需要自定义更多(类型映射器、调用约定),则需要在静态初始化程序块中创建选项映射。