- Java客户代码实现和native方法声明属于java层,使用java编译器编译;
- JNI接口实现代码和c++库属于c++层,使用G++编译。
这里假定C++类库已经预编译好了,有现成的so库和c接口使用。首先明确一点就是,我们要为C++库封装一个java接口,也即在java层使用C++库暴露的所有函数,那么:
- 第一步就是创建一个java类,并按照c++库的接口函数声明,创建所有的native本地接口函数声明(可以是static的)。
- 第二步,将这些本地接口声明映射为C++ JNI接口声明,这一步是通过java提供的工具按照既定的映射机制自动生成。这也就保证了java层能正确找到c++实现。
- 第三步,实现第二步自动生成的c++ JNI接口函数,在这些接口实现中,按照需要调用c++类库的接口函数,以使用特定的功能并拿到需要的结果。所以,这里要注意的一点是,c++ JNI接口函数实现会编译为一个单独的动态库,并且该动态库动态链接C++类动态库。(这里没有尝试过静态库,按道理应该也是可以的)。此外,在c++ JNI函数实现中按照类型签名规则,我们可以获取到从java层传入的参数,也可以返回特定的数据到Java层。
- 第四步,在java应用层使用加载第三步编译生成的jni so库,即可间接调用到c++库函数。
PS:
-
String 字符串函数操作
-
JNI 支持将 jstring 转换成 UTF 编码和 Unicode 编码两种。因为 Java 默认使用 Unicode 编码,而 C/C++ 默认使用 UTF 编码。所以使用将 jstring 转换成 UTF 编码的字符串。其中,jstring 类型参数就是我们需要转换的字符串,而 isCopy 参数的值在实际开发中,直接填 0或NULL就好了,表示深拷贝。
-
当调用完 方法时别忘了做完全检查。因为 JVM 需要为产生的新字符串分配内存空间,如果分配失败就会返回 NULL,并且会抛出 OutOfMemoryError 异常,所以要对 GetStringUTFChars 结果进行判断。
-
当使用完 UTF 编码的字符串时,还不能忘了释放所申请的内存空间。调用 方法进行释放。
-
除了将 jstring 转换为 C 风格字符串,JNI 还提供了将 C 风格字符串转换为 jstring 类型。
-
通过函数可以将 UTF 编码的 C 风格字符串转换为 jstring 类型,通过函数可以将 Unicode 编码的 C 风格字符串转换为 jstring 类型。这个 jstring 类型会自动转换成 Java 支持的 Unicode 编码格式。
实践出真知,分别建立一个c++工程和java工程,源码github地址。
结构目录如下:
整体构建流程如下:
- 在java工程下创建和C++类库同名(非必须)的java类源文件,并声明和c++工程接口统一的native成员函数;
使用命令生成naive本地接口.h头文件。将其拷贝到c++工程下。 - 在c++工程下实现jni接口头文件中的函数声明,实现中调用c接口间接完成特定能力调用,编译为libjnilib.so,并链接原始c++库的动态库。
- 回到java工程中,在native接口所在的那个类中,添加jni库加载代码:
- java 测试代码调用,使用如下脚本: