- 系统编程
- 后端
【免费下载链接】jna
Java Native Access
导读
本文基于 www/Mappings.md 展开,系统讲解 JNA(Java Native Access)中 Java 类型与原生 C 类型的默认映射规则:从基础类型对照表、平台相关的尺寸差异(如long、wchar_t),到NativeLong等适配类型的底层实现,再到通过TypeMapper、NativeMapped定制映射的高级用法。读完本文,你将能准确为任何 C 函数接口选择对应的 Java 类型,并能用自定义映射解决 BOOL、stdcall 命名等平台兼容问题。
一、核心原则:等宽映射
Java primitive types (and their object equivalents) map directly to the native C type of the same size.
JNA 默认映射的第一条原则是:Java 原始类型(及其包装类型)直接映射到同尺寸的原生 C 类型。也就是说,JNA 不是按“语义”而是按“位宽”来建立对应关系——只要 Java 类型与 C 类型的字节数一致,就可以直接对应。
这条原则的源码依据可以在 Native.getNativeSize(Class) 中看到:JNA 为每个 Java 类维护一张“原生尺寸表”,例如byte→1 字节、short→2 字节、int→4 字节、long→8 字节等,调用前的参数装箱、返回值解包都依赖这张表。值得注意的是,源码中boolean/Boolean的默认原生尺寸被硬编码为4 字节(即按 32 位整数处理),这正对应下表中int → boolean → BOOL的映射。
二、默认类型映射对照表
下表来自 www/Mappings.md,列出了 JNA 默认的 Java ↔ Native 类型映射关系:
| Native Type | Size | Java Type | Common Windows Types |
|---|---|---|---|
| char | 8-bit integer | byte | BYTE, TCHAR |
| short | 16-bit integer | short | WORD |
| wchar_t | 16/32-bit character | char | TCHAR |
| int | 32-bit integer | int | DWORD |
| int | boolean value | boolean | BOOL |
| long | 32/64-bit integer | NativeLong | LONG |
| long long | 64-bit integer | long | __int64 |
| float | 32-bit FP | float | |
| double | 64-bit FP | double | |
| char* | C string | String | LPCSTR |
| void* | pointer | Pointer | LPVOID, HANDLE, LPXXX |
原文档还给出了两条补充规则:
- 无符号类型使用与有符号类型相同的映射:例如 C 的
unsigned int依然对应 Java 的int,unsigned short对应short,位宽不变即可; - C 枚举通常可与
int互换:枚举成员在底层就是整数值,JNA 接口中直接使用int或自定义枚举类型皆可。
三、关键映射背后的平台差异
3.1wchar_t→char:宽字符的尺寸分歧
表中wchar_t标注为“16/32-bit character”。在 Windows 上wchar_t是 16 位(对应 UTF-16 编码),而在多数类 Unix 平台(Linux、macOS)上它是 32 位(对应 UTF-32)。JNA 在运行期通过原生代码探测实际尺寸:
Native.WCHAR_SIZE字段保存当前平台的wchar_t字节数(见 Native.java);getNativeSize中char.class / Character.class返回的就是WCHAR_SIZE(见 Native.java)。
因此,JNA 的char参数/字段在 Java 侧始终是单个char,在原生侧则按平台自动适配 2 或 4 字节,这让跨平台接口声明保持统一。
3.2long→NativeLong:C long 的可变位宽
C 的long在 64 位 Unix 系统上是 64 位,在 Windows(LLP64 模型)上却仍是 32 位。若直接用 Java 的long(固定 64 位)去映射,Windows 上就会错位。JNA 为此专门设计了NativeLong:
public class NativeLong extends IntegerType { /** Size of a native long, in bytes. */ public static final int SIZE = Native.LONG_SIZE; public NativeLong() { this(0); } public NativeLong(long value) { this(value, false); } public NativeLong(long value, boolean unsigned) { super(SIZE, value, unsigned); } }NativeLong.SIZE直接取自Native.LONG_SIZE(运行期探测值,见 Native.java)。它的基类IntegerType实现了NativeMapped接口:setValue根据size(1/2/4/8)做截断与符号处理,toNative()返回对应的装箱数值,fromNative()通过Klass.newInstance重建实例并回填值。使用示例:
public interface LibC extends Library { // C 原型: long lseek(int fd, long offset, int whence); NativeLong lseek(int fd, NativeLong offset, int whence); }IntegerType还支持构造时传入unsigned=true表示无符号语义,内部按0xFF/0xFFFF/0xFFFFFFFF掩码保留位模式,适合处理unsigned long、unsigned int等场景。
3.3void*→Pointer与char*→String
char*:JNA 将其视为 C 字符串并自动映射为 JavaString,负责编码/解码与\0终止符处理;对应 Windows 的LPCSTR。若需要宽字符串,可使用WString(对应wchar_t*/LPCWSTR)。void*:不透明指针映射为Pointer,Windows 上的LPVOID、HANDLE以及各类LPXXX指针均属此类。从getNativeSize的实现可见,Pointer、String、WString、Callback、Buffer在栈上统一按POINTER_SIZE(指针宽度)传递。
四、深入源码:默认映射如何被查找
映射查找入口有两个:Function(方法参数与返回值)与Structure(结构体字段)。以函数调用为例,Function.java 对返回类型调用mapper.getFromNativeConverter(returnType),Function.java 对每个参数调用mapper.getToNativeConverter(type);若未配置TypeMapper,JNA 走内置默认路径。
TypeMapper接口定义了两个方向的方法(见 TypeMapper.java):
public interface TypeMapper { FromNativeConverter getFromNativeConverter(Class<?> javaType); ToNativeConverter getToNativeConverter(Class<?> javaType); }默认实现DefaultTypeMapper维护toNativeConverters与fromNativeConverters两个注册表,支持三种注册方式:
addToNativeConverter(cls, converter):注册 Java→Native 单向转换;addFromNativeConverter(cls, converter):注册 Native→Java 单向转换;addTypeConverter(cls, converter):注册双向转换。
DefaultTypeMapper的getAltClass方法会自动将int.class与Integer.class、boolean.class与Boolean.class等原始类型/包装类型配对注册,因此注册一次即可覆盖两种写法。查找时按注册顺序做isAssignableFrom匹配(见 DefaultTypeMapper.java),支持父类/接口级别的宽泛匹配。
ToNativeConverter的nativeType()返回值必须是 JNA 支持的原生类型集合:Pointer、Boolean、Byte、Short、Character、Integer、NativeLong、Long、Float、Double、Structure、String、WString以及Buffer/原始类型数组(注意后两者不支持 Direct 映射模式),见 ToNativeConverter.java。
五、自定义映射的三种方式
5.1 通过 TypeMapper 定制(接口级映射)
TypeMapper 通常用于解决“整个接口统一类型转换”的需求,最典型的就是Javaboolean↔ Win32BOOL(32 位整数)。使用方式是把映射器实例作为TYPE_MAPPER选项传给Native.load:
Map<String, Object> options = new HashMap<>(); options.put(Library.OPTION_TYPE_MAPPER, new W32APITypeMapper()); MyLibrary lib = Native.load("mylib", MyLibrary.class, options);相关选项键定义在 Library.java:OPTION_TYPE_MAPPER = "type-mapper"。Native.load会在加载库时读取该选项并传递给后续的函数调用(见 Native.java)。
DefaultTypeMapper适合作为自定义映射器的基类,在构造器中追加自己的转换规则。仓库中的 TypeMapperTest.java 提供了可直接运行的完整示例,例如把Boolean映射成魔法值整数(见 TypeMapperTest.java):
DefaultTypeMapper mapper = new DefaultTypeMapper(); mapper.addToNativeConverter(Boolean.class, new ToNativeConverter() { @Override public Object toNative(Object arg, ToNativeContext ctx) { return Integer.valueOf(Boolean.TRUE.equals(arg) ? MAGIC : 0); } @Override public Class<?> nativeType() { return Integer.class; } }); TestLibrary lib = Native.load("testlib", TestLibrary.class, Collections.singletonMap(Library.OPTION_TYPE_MAPPER, mapper)); assertEquals(MAGIC, lib.returnInt32Argument(true));测试中还演示了双向转换(Integer → Boolean,见 TypeMapperTest.java)、String ↔ WString转换(TypeMapperTest.java),以及Structure 字段级别的映射:把结构体中的boolean字段映射为 4 字节整数后,Structure.size()变为 4、写入内存的值变为 1/0(TypeMapperTest.java)。
5.2 通过 NativeMapped 实现(类级映射)
如果只有某一类特定对象需要特殊映射,可以让该类型实现NativeMapped接口。它要求实现三个方法:
public interface NativeMapped { Object fromNative(Object nativeValue, FromNativeContext context); Object toNative(); Class<?> nativeType(); }NativeLong、IntegerType的子类正是走这条路。实现类必须提供无参构造器(NativeMappedConverter通过反射实例化)。典型的枚举映射写法:
public enum Status implements NativeMapped { OK(0), ERROR(-1); private final int code; Status(int code) { this.code = code; } @Override public Object toNative() { return code; } @Override public Object fromNative(Object v, FromNativeContext c) { return valueOf(((Number) v).intValue()); } @Override public Class<?> nativeType() { return Integer.class; } }这样接口方法签名中直接使用Status即可,JNA 会经NativeMappedConverter自动完成双向转换(枚举的完整用法同样可见于 TypeMapperTest.java)。
5.3 通过 FunctionMapper 定制(函数名映射)
除了类型映射,JNA 还允许定制Java 方法名 → 原生函数名的映射,对应选项键OPTION_FUNCTION_MAPPER = "function-mapper"(见 Library.java)。典型实现是 Windows 平台的StdCallFunctionMapper:stdcall 约定要求导出函数名带有@字节数后缀,该实现会计算每个参数的原生栈尺寸并拼出形如FuncName@12的装饰名,找不到时再尝试带下划线的_FuncName@12,最后回退到未装饰名(见 StdCallFunctionMapper.java)。
Map<String, Object> options = new HashMap<>(); options.put(Library.OPTION_FUNCTION_MAPPER, new StdCallFunctionMapper()); MyWin32Lib lib = Native.load("user32", MyWin32Lib.class, options);需要留意:若同时使用了自定义类型映射,getArgumentNativeStackSize会根据NativeMapped的实际原生类型推算栈大小;对于原生类型尺寸未知的自定义类,可通过覆写该方法补充(见 StdCallFunctionMapper.java)。
六、实践建议与常见误区
long别用 Javalong直连:除非你明确目标平台是 64 位 Unix(此时long恰好 64 位),跨平台代码应统一使用NativeLong,否则 Windows 上会栈错位;boolean默认是 4 字节整数:getNativeSize中boolean/Boolean硬编码为 4,C 头文件里的BOOL就按此处理;若是 C99 的_Bool(1 字节),需要自定义映射;char不等同于 1 字节:JNA 的char按wchar_t尺寸(2 或 4 字节)处理,映射 C 的 8 位char应使用byte;- 映射器顺序敏感:
DefaultTypeMapper按注册顺序查找,先注册的转换器优先命中,注册宽泛类型(如CharSequence)时要放在具体类型之后; - 无符号类型用同尺寸有符号类型承接:仅当需要访问无符号位模式时才考虑
IntegerType(..., true)等带unsigned标志的包装。
七、进一步阅读
- 默认映射总表原文档:www/Mappings.md
- 自定义映射官方说明:www/CustomMappings.md
- 映射器接口与默认实现:TypeMapper.java、DefaultTypeMapper.java、NativeMapped.java
- 平台尺寸探测与原生尺寸表:Native.java
- 可运行测试样例:TypeMapperTest.java
- stdcall 函数名映射器:StdCallFunctionMapper.java
- 系统编程
- 后端
【免费下载链接】jna
Java Native Access
相关推荐
BLOOM-3B模型深度解析:30亿参数如何实现45种自然语言与12种编程语言支持 🚀
BLOOM 3B模型深度解析:30亿参数如何实现45种自然语言与12种编程语言支持 🚀 想要了解如何用30亿参数的AI模型支持45种自然语言和12种编程语言吗
PyO3 类型转换对照表全解析:Rust 类型与 Python 类型的一一映射
PyO3 类型转换对照表全解析:Rust 类型与 Python 类型的一一映射 导读 :在 PyO3 中编写可被 Python 调用的函数( pyfunctio
开发工具TypeGraphQL类型转换:自定义类型映射规则
TypeGraphQL类型转换:自定义类型映射规则 TypeGraphQL通过类型映射机制实现TypeScript类型与GraphQL标量 Scalar 的转换
后端GraphQLAPI设计
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考