学习背景
最近由于公司项目的需要,要求把一个第三方c语言的so库封装成java接口,并打包成jar,最终运行在linux平台。
整个学习和封装实现大约花了半个月,中间遇到了各种问题,特此总结。
出于公司项目安全的要求,后续总结不给出实际的文件命名,而重新用一些简单的替换命名来表述。
实现流程
- 已有一个第三方so库(a.so)、对应的头文件(记为a.h)
- 根据头文件自行定义对应的java接口,a_interface.java,编写文档
- 实现对应的a_imp.java源文件
- 类初始化时需要加载jni格式c文件编译生成的so库(jni.so)
- 如果有依赖库要先加载对应依赖库
- public 前缀修饰符对应 a_interface.java接口定义的实现
- private native
前缀修饰符对应本地已有函数的声明,该函数的实现在jni.so中
- 和public一一对应
- 类初始化时需要加载jni格式c文件编译生成的so库(jni.so)
- 命令行执行javah -jni -encoding UTF-8 [包名.子包名.a_interface.java]
- 将生成包名_子包名_a_interface.h jni格式的头文件
- 创建并实现包名_子包名_a_interface.c jni格式的源文件
- 需要包含a.h
- 需要加载a.so
- 编译配置推荐使用Makefile或CMakeLists实现
- 在jni格式源文件中调用a.so中的接口,并根据a_imp.java的private native函数的形式来返回变量
- 编译生成jni.so
- 在a_imp.java对应的java项目中配置打包,导出a.jar包
- 编写java测试demo,导入jar包、相关so库,调用a_interface的接口
调用流程
这里考虑String作为入参和返回的情况
- 外部使用者调用a_imp.java的接口,传入String对象
- public 把String对象传入对应的private native 函数中
- native函数调用jni.so对应函数,此时java中的String对象将自动转换为c语言的jString结构体变量
- 调用jni相关转换函数,把jString转换为char数组
- 把char数组作为入参传入到a.so对应的函数中,得到返回的char数组
- 利用jni相关转换函数,把char数组转化为jString变量并返回
- native函数也就返回了java的String对象
- public接口返回String对象
此外如果入参是java自定义类,则对应jni的jObject结构体;
如果入参是int,则对应jni的jint类型;
jni提供了完善的类型转换接口,可自行搜索资料