前言
只要你用过了 JNA (java native access) , 那你可能就再也不想用 JNI 了
实际上, JNA 搞定了 JNI 中最麻烦的数据类型映射, 可以让我们进行高效的开发, 不用再去写各种的转换接口.
- char*
- string
- 数组
- 结构体
上面的数据类型它都支持
可能有人会问 JNA 能完全代替 JNI 么? 不能, JNA只能实现Java访问C函数,如果你想实现C语言调用 Java 代码, 你还是需要使用 JNI 技术。
但其实在很大程度上已经够用了, 因为在很多应用领域都是由 C++算法工程师提供库 (.so/.dll) , java 工程师只需要负责调用就可以了
JNA 有什么用? 吹一波?
就一个 简化 jni 开发
想想当年写 jni 的时候, 下面这样的代码要写几百行, 你就知道我有多痛苦了
当然最痛苦的还不是这个
在开发中, 作为一个JAVA 程序员你会遇到各种各样奇葩的问题
还包含一部分你无法理解的C++问题
- 类型转换
- linux windows多环境编译
- 内存泄漏
- 异常处理
- 各种找不到原因的报错
- debug 困难
- ...
而如果使用 JNA, 你可能只需要这样:
-
你不需要通过 javah 生成头文件, 不需要给它写实现
-
不需要在 windows/linux 环境各自编译成 .dll/.so 来调用真正的函数
-
只需要声明一个接口, 其他的事情让 JNA 做好就行
public class HelloWorld { public interface CLibrary extends Library { CLibrary INSTANCE = Native.load((Platform.isWindows() ? "msvcrt" : "c"), CLibrary.class); void printf(String format, Object... args); } public static void main(String[] args) { CLibrary.INSTANCE.printf("Hello, World "); } } // 该源码来自 https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md
哪些项目使用了 JNA
- Apache Cassandra:大型NoSQL数据存储
- JetBrains的IntelliJ IDEA
- NetBeans IDE
...
每天都在用的 IDEA 都用了 JNA =.= 你还怕什么?
数据类型映射
了解一下 C -> java 的数据类型映射, 帮助你更好的完成之后的练习
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 |
from JNA - Default Type Mappings
你可能会发现表中没有 native 的 boolean 类型, 默认上 boolean = true
会被默认映射成 -1(int)
, 在 C 中打印出来是 255. 所以我一般在定义接口时, 会避免使用 bool . 如果想自定义 boolean 映射, 可以参考 JNA maps Java boolean to -1 integer?
代码示例
本代码示例基于 64位 win10 + vs 2019
如果遇到 dll 依赖问题( UnsatisfiedLinkError ), 请下载 micro soft vc 运行库 或者使用 Dependency walker 查看 dll 依赖缺失情况
可以 clone 一下我的项目:
java 部分: https://github.com/giraffe-tree/jna-func
c++ 部分(vs 项目): https://github.com/giraffe-tree/jna-c
max
先来个简单的例子
先将下面的依赖加入你的 java maven 项目
<dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>5.3.1</version> </dependency>
// c++ 需要在 vs 中编译成 dll int max(int num1, int num2) { return num1 > num2 ? num1 : num2; }
// java public interface JnaLibrary extends Library { // JNA 为 dll 名称 JnaLibrary INSTANCE = Native.load("JNA", JnaLibrary.class);
将编译好的 JNA.dll
放入 resources 文件夹下的 win32-x86-64
目录中 (我使用的是 64位 windows), JNA 会自动到 win32-x86-64
中去找 JNA.dll
运行 main
函数即可
public static void main(String[] args) { int max = JnaLibrary.INSTANCE.max(100, 200); // out: 200 System.out.println(max); }
primitive array
// java public interface JnaLibrary extends Library { // JNA 为 dll 名称 JnaLibrary INSTANCE = Native.load("JNA", JnaLibrary.class); void testArray(short[] vals, int len); }
// c++ void testArray(uint16_t* vals, int len) { for (int j = 0; j < len; j++) { printf("vals[%d]: %d ", j, vals[j]); } }
JnaLibrary.INSTANCE.testArray(new short[]{1, 2, 3, 4}, 4); // out: vals[0]: 1 vals[1]: 2 vals[2]: 3 vals[3]: 4
值传递与引用传递
通过值传递对象的时候需要注意
- 对象需要继承
Structure
, 且它的属性必须为 public- Structure fields corresponding to native struct fields must be public. If your structure is to have no fields of its own, it must be declared abstract.
- JNA 有时候会判断错误, 导致原本的值传递, 变成引用传递, 从而报出
Invalid memory access
的异常, 这时候最好实现一下Structure.ByValue
接口
- FieldOrder 需要按顺序写, 否则会报出
Invalid memory access
// java public interface JnaLibrary extends Library { // JNA 为 dll 名称 JnaLibrary INSTANCE = Native.load("JNA", JnaLibrary.class); // 实际测试下来 void printUser(User.ByValue user); 也是可以的 void printUser(User user); void printUserRef(User user); @Structure.FieldOrder({"name", "height", "weight"}) public static class User extends Structure { public static class UserValue extends User implements Structure.ByValue { public UserValue(String name, int height, double weight) { super(name, height, weight); } } public User(String name, int height, double weight) { this.name = name; this.height = height; this.weight = weight; } public String name; public int height; public double weight; } }
// .h struct User { char* name; int height; double weight; }; void printUser(User user); void printUserRef(User& user); // cpp void printUser(User user) { printf("printUser user: %s height: %d weight: %.2f ", user.name, user.height, user.weight); } void printUserRef(User& user) { printf("printUserRef user: %s height: %d weight: %.2f ", user.name, user.height, user.weight); }
JnaLibrary.User.UserValue user1 = new JnaLibrary.User.UserValue("user1", 186, 65.2); JnaLibrary.INSTANCE.printUserRef(user1); JnaLibrary.INSTANCE.printUser(user1); // out: printUserRef user: user1 height: 186 weight: 65.20 printUser user: user1 height: 186 weight: 65.20
Pointer
// java public interface JnaLibrary extends Library { // JNA 为 dll 名称 JnaLibrary INSTANCE = Native.load("JNA", JnaLibrary.class); void testStruct(ArrInfo arrInfo); @Structure.FieldOrder({"vals", "len"}) public static class ArrInfo extends Structure { public Pointer vals; public int len; public ArrInfo(Pointer vals, int len) { this.vals = vals; this.len = len; } } }
// .h struct ArrInfo { uint16_t* vals; int len; }; void testStruct(ArrInfo arrInfo); // cpp void testStruct(ArrInfo arrInfo) { for (int j = 0; j < arrInfo.len; j++) { printf("arrInfo[%d]: %d ", j, arrInfo.vals[j]); } }
// java main test int len = 3; int shortSize = Native.getNativeSize(Short.class); Pointer pointer = new Memory(len * shortSize); for (int i = 0; i < len; i++) { pointer.setShort(shortSize * i, (short) i); } JnaLibrary.ArrInfo arrInfo = new JnaLibrary.ArrInfo(pointer, len); JnaLibrary.INSTANCE.testStruct(arrInfo); // out arrInfo[0]: 0 arrInfo[1]: 1 arrInfo[2]: 2
关于 JNA 调试
虽然 JNA 相比于 JNI 好用很多, 但我在使用的过程中还是遇到一些"坑"
这些 bug 常常很难直接找出, JNA 统一都报了一个 Invalid memory access
, 导致我们找不到真正错误的原因. 这时候就需要调试了
我使用的开发环境是 IDEA + jdk8 + VS2019
步骤
先简单讲下步骤, 具体图文可以看下面的实战
- 通过 vs 编译 debug 版本的 dll , 放入 java 项目指定的目录下 (我这里是
resources/win32-x86-64/JNA.dll
) - 启动 java 程序, 停止在你指定的断点上
jps -l
找到 java 程序的 pid- 在 vs 中
ctrl+alt+p
附加到进程, 选择刚刚找到的 pid, 点击附加- 其实一般是最上面的那个 java 进程, 就是你刚刚运行起来的那个
- 点击
j
可以快速查找
- 在 vs 中设置一个断点
- 在 idea 中继续debug, 它会跳到 vs 的debug 界面中
- 在 vs 中点击继续就可以接着调试啦
实战
在 idea 中进入调试
在下图中, jps -l
找到 java 程序的 pid = 10560
ctrl+alt+p
附加到进程, 选择刚刚找到的 pid, 点击附加此时断点不会命中
yeah, 调试完成 =.=
其他问题
C++ 中 printf 控制台打印缺失
目前测试下来, 可能存在 java 主线程停止, 但 C++ print 缓存区未被清空/不输出的情况, 导致控制台打印内容缺失
在这种情况下, 请延长 java 主线程运行的时间.
在C++调试时, 遇到未加载 jvm.pdb
目前测试下来, 在 vs 中 debug 继续后会报出一个 未加载 jvm.pdb
, 我确实没有找到这个文件
但还可以继续调试, 没啥大问题
不过什么时候来个调试 openjdk 想想应该蛮有趣的 哈哈
Invalid memory access
这个问题怎么说呢, JNA 好多地方都能报出这个错误, 我遇到这个错误时, 大部分都是我的java 参数 -> C++ 参数的映射问题
包括参数类型写错, 顺序写错等, 仔细检查映射关系就能解决
dll 兼容性问题
java.lang.UnsatisfiedLinkError: 找不到指定的模块。
这个错误我遇到过的情况分两种
- 我调用的 dll 没有放进指定目录
- 比如我的平台是
win32-x86-64
, 但是没有放入这个目录中 - 这种情况下, jna 会提示找不到指定目录的资源文件比较好解决
- 比如我的平台是
- 我调用的 dll 依赖的其他 dll 没有找到
- 这个情况, 我在 win10 下编译, 然后在 windower server 2012 上运行时遇到过
- 通过解决方案是这样
- 通过 Dependency walker 查看 dll 依赖缺失情况
- 补全缺失的 dll
- 当时我的情况是 缺失了 vc140, 然后我下载 micro soft vc 运行库 就解决了这个问题
个人网站文章链接: https://giraffetree.me/2019/10/28/jna_tutorial/ 欢迎留言~
作者:长脖子树
链接:https://www.jianshu.com/p/ead89497c403
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。