接上一篇关于JNI的基本入门Java通过JNI调用C/C++动态链接库之HelloWorld,简述给JNI函数传递参数,以及通过JNI函数返回值的方式。
JNI类型映射
首先来回顾一下上一篇基础文章中通过javah生成的头文件的以下内容:
1 | /* |
发现注释中给出了类名hello
,是我们JAVA程序的类名,后面跟着方法名以及方法的签名,注意方法的签名类型为()V
,表示该方法不接收任何参数,且返回类型为void,即没有返回值。
JNI定义了一些基本类型用于在java与native代码之间的映射,这些基本类型都是j
开头,后面跟java中的实际类型,具体的基本类型及方法签名类型如下表所示:
Native类型 | Java类型 | 描述 | 签名类型 |
---|---|---|---|
unsigned char | jboolean | unsigned 8 bits | Z |
signed char | jbyte | signed 8 bits | B |
unsigned short | jchar | unsigned 16 bits | C |
short | jshort | signed 16 bits | S |
long | jint | signed 32 bits | I |
long long __int64 |
jlong | signed 64 bits | J |
float | jfloat | 32 bits | F |
double | jdouble | 64 bits | D |
void | V |
方法签名的形式即为(参数签名)返回值类型签名
。例如一个形如double func(int a, int b)
的方法签名为(II)D
另外还有几个比较特殊的签名,如object
签名为L{用/分割的完整类名}
如Ljava/lang/String
表示一个String对象。Array
的签名为[{相应类型的签名}
,如[D
,表示类型为D的数组,[Ljava/lang/String
表示String对象的数组。
当然JNI也定义了很多引用类型的映射,其中jobject
对应于java.lang.Object
,以及其之类型:
jclass
—java.lang.Class
jstring
—java.lang.String
jthrowable
—java.lang.Throwable
jarray
与java中的Array对应,由于Array在java中支持8种基本数据类型,所以相应的有8种Array,分别为jintArray
、jbyteArray
、jshortArray
、jlongArray
、jfloatArray
、jdoubleArray
、jcharArray
和jbooleanArray
。以及一个对象类型的Array:jobjectArray
。
查看jni.h
头文件会发现里面都是使用typedef进行的定义:
1 | // 头文件"jni.h"中的声明 |
现在可以清楚的知道JNI在java代码和native代码之间做了类型转换,以使两种语言之间的数据类型可以互通。
基本类型参数传递
举一个例子说明,程序的功能为给java程序传递两个int做为参数,将该参数传递给native函数,native函数累加两个参数之间的数字,然后取平均后将结果返回给java程序并输出。
java代码
1 | //TestJint.java |
头文件
通过javah
生成头文件内容如下:TestJint.h
1 | /* DO NOT EDIT THIS FILE - it is machine generated */ |
可以看到其中的方法签名为(II)D
,函数参数列表中多了2个jint类型的形参。
C++代码
注意其中传递进行的形参类型都是j开头的JNI类型,而在函数内部则可以使用任何你需要的C++数据结构。
1 | //TestJint.cpp |
编译并运行
编译动态链接库
1 | g++ -fPIC -shared -o libmyjint.so -I /usr/lib/jvm/java-8-oracle/include/ -I /usr/lib/jvm/java-8-oracle/include/linux/ TestJint.cpp |
编译java
1 | javac TestJint.java |
运行:
1 | java -Djava.library.path=. TestJint 5 8 |
输出为:
1 | In C++, the sum 5 ... 8 = 26 |
String类型的参数传递
String其实与基本类型的参数传递类似,只是需要通过JNIEnv参数调用调用jni中的函数进行转换成符合C/C++的字符串后再操作。因为java中的字符串是以unicode表示,而c/c++中是以utf方式表示。
举例说明,实现功能为通过向java程序传递字符串参数,然后该字符串传递给native函数,native函数将其反转后与在native函数中获取到的用户输入一起返回给java,并输出
java程序
如下,并没有太大区别:
1 | // TestJstr.java |
生成头文件
通过javah生成头文件,头文件中的函数声明如下:
1 | /* |
可以看到方法签名与我们之前的讨论一样,函数接收一个jstring参数
C++代码
注意里面的函数调用与oracle官方给的文档并不完全致,可以通过查看jni.h头文件来获取相关函数
1 | //TestJstr.cpp |
注意调用方法为env->GetStringUTFChars(jStr, NULL);
,官方文档中需要传递三个参数,是C风格的函数调用方式,C和C++的调用语法如下:
- C syntax:
cls = (*env)->FindClass(env, "Sample2");
- C++ syntax:
cls = env->FindClass("Sample2");
更多字符串操作相关函数参见https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#string_operations
编译并运行
编译java代码:
1 | javac TestJstr.java |
编译C++代码:
1 | g++ -fPIC -shared -o libmystr.so -I /usr/lib/jvm/java-8-oracle/include/ -I /usr/lib/jvm/java-8-oracle/include/linux/ TestJstr.cpp |
运行:
1 | java -Djava.library.path=. TestJstr HelloWorld |
中间的输入部分,输入Maxwi
,输出如下:
1 | Enter a string in C++: Maxwi |
完全正确运行
通过JNI传递数组
数组的传递同时是通过调用JNI函数进行转换后进行正常的操作即可,数组相关的JNI操作函数参见Oracle官网https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#array_operations
下面直接上代码,实现功能为从java中传递一个int数组到native中,native计算出其和以及平均值之后以数组形式返回。
java代码
1 | //TestJarr.java |
生成的头文件
只写函数:
1 | /* |
C++代码
注意里面的转换,C++数组转成jintArray时需要先分配内存,再拷贝数据,而不是直接有函数进行转换
1 | //TestJarr.cpp |
编译并运行
1 | javac TestJarr.java |
运行结果:
1 | In java, the sum is 94.0 |
这些内容对于我当前已经够用,关于访问native访问java对象变量以及方法回调的内容请参见下面的参考文档。
以上这些例子都比较简单,实际上可以通过将复杂的计算放在C++函数中运行,最后通过native函数包裹该C++函数之后在JNI中调用,这样需要处理的内容会少很多,而且应该速度也会比较快,少去了那些过多的转换工作。
参考
1.Java Native Interface (JNI)
2.Wikipdeia-Java Native Interface
3.Oracle-JNI
4.Java programming with JNI