接上一篇关于JNI的基本入门Java通过JNI调用C/C++动态链接库之HelloWorld ,简述给JNI函数传递参数,以及通过JNI函数返回值的方式。
JNI类型映射 首先来回顾一下上一篇基础文章中通过javah生成的头文件的以下内容:
1 2 3 4 5 6 7 JNIEXPORT void JNICALL Java_hello_helloWorld (JNIEnv *, jobject) ;
发现注释中给出了类名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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 typedef unsigned char jboolean;typedef unsigned short jchar;typedef short jshort;typedef float jfloat;typedef double jdouble;typedef jint jsize;struct _jobject ;typedef struct _jobject *jobject;typedef jobject jclass;typedef jobject jthrowable;typedef jobject jstring;typedef jobject jarray;typedef jarray jbooleanArray;typedef jarray jbyteArray;typedef jarray jcharArray;typedef jarray jshortArray;typedef jarray jintArray;typedef jarray jlongArray;typedef jarray jfloatArray;typedef jarray jdoubleArray;typedef jarray jobjectArray;typedef int jint;#ifdef _LP64 typedef long jlong;#else typedef long long jlong;#endif typedef signed char jbyte;
现在可以清楚的知道JNI在java代码和native代码之间做了类型转换,以使两种语言之间的数据类型可以互通。
基本类型参数传递 举一个例子说明,程序的功能为给java程序传递两个int做为参数,将该参数传递给native函数,native函数累加两个参数之间的数字,然后取平均后将结果返回给java程序并输出。java代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class TestJint { static { System.loadLibrary("myjint" ); } private native double avg (int a, int b) ; public static void main (String args[]) { if (args.length < 2 ) { System.out.println("Input Error!" ); return ; } int a = Integer.parseInt(args[0 ]); int b = Integer.parseInt(args[1 ]); System.out.println(a + " + ... + " + b + " / " + (b - a + 1 ) + " = " + new TestJint ().avg(a, b)); } }
头文件 通过javah生成头文件内容如下:TestJint.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <jni.h> #ifndef _Included_TestJint #define _Included_TestJint #ifdef __cplusplus extern "C" {#endif JNIEXPORT jdouble JNICALL Java_TestJint_avg (JNIEnv *, jobject, jint, jint) ;#ifdef __cplusplus } #endif #endif
可以看到其中的方法签名为(II)D,函数参数列表中多了2个jint类型的形参。
C++代码 注意其中传递进行的形参类型都是j开头的JNI类型,而在函数内部则可以使用任何你需要的C++数据结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 #include "TestJint.h" #include <iostream> JNIEXPORT jdouble JNICALL Java_TestJint_avg (JNIEnv *, jobject, jint a, jint b) { double res = 0.0 ; for (int i = a; i <= b; ++i) { res += i; } std::cout << "In C++, the sum " << a << " ... " << b << " = " << res << std::endl; return res / double (b - a + 1 ); }
编译并运行 编译动态链接库
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 java -Djava.library.path=. TestJint 5 8
输出为:
1 2 In C++, the sum 5 ... 8 = 26 5 + ... + 8 / 4 = 6.5
String类型的参数传递 String其实与基本类型的参数传递类似,只是需要通过JNIEnv参数调用调用jni中的函数进行转换成符合C/C++的字符串后再操作。因为java中的字符串是以unicode表示,而c/c++中是以utf方式表示。 举例说明,实现功能为通过向java程序传递字符串参数,然后该字符串传递给native函数,native函数将其反转后与在native函数中获取到的用户输入一起返回给java,并输出
java程序 如下,并没有太大区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class TestJstr { static { System.loadLibrary("mystr" ); } private native String andReverse (String msg) ; public static void main (String args[]) { if (args.length < 1 ) { System.out.println("Input Error!" ); return ; } String res = new TestJstr ().andReverse(args[0 ]); System.out.println("In Java, Result: " + res); } }
生成头文件 通过javah生成头文件,头文件中的函数声明如下:
1 2 3 4 5 6 7 JNIEXPORT jstring JNICALL Java_TestJstr_andReverse (JNIEnv *, jobject, jstring) ;
可以看到方法签名与我们之前的讨论一样,函数接收一个jstring参数
C++代码 注意里面的函数调用与oracle官方给的文档并不完全致,可以通过查看jni.h头文件来获取相关函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include "TestJstr.h" #include <iostream> #include <string> #include <algorithm> JNIEXPORT jstring JNICALL Java_TestJstr_andReverse (JNIEnv *env, jobject obj, jstring jStr) { const char *cStr = env->GetStringUTFChars (jStr, NULL ); if (NULL == cStr) return NULL ; std::string tstr = cStr; env->ReleaseStringUTFChars (jStr, cStr); std::reverse (tstr.begin (), tstr.end ()); std::string ustr; std::cout << "Enter a string in C++: " ; std::cin >> ustr; return env->NewStringUTF (("Reverse: " + tstr + " C++ string: " + ustr).c_str ()); }
注意调用方法为env->GetStringUTFChars(jStr, NULL);,官方文档中需要传递三个参数,是C风格的函数调用方式,C和C++的调用语法如下:
编译并运行 编译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 2 Enter a string in C++: Maxwi In Java, Result: Reverse: dlroWolleH C++ string: 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class TestJarr { static { System.loadLibrary("myjarr" ); } private native double [] sumAndAvg(int [] num); public static void main (String args[]) { int [] num = {8 , 19 , 22 , 45 }; double [] result = new TestJarr ().sumAndAvg(num); System.out.println("In java, the sum is " + result[0 ]); System.out.println("In java, the average is " + result[1 ]); } }
生成的头文件 只写函数:
1 2 3 4 5 6 7 JNIEXPORT jdoubleArray JNICALL Java_TestJarr_sumAndAvg (JNIEnv *, jobject, jintArray) ;
C++代码 注意里面的转换,C++数组转成jintArray时需要先分配内存,再拷贝数据,而不是直接有函数进行转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include "TestJarr.h" JNIEXPORT jdoubleArray JNICALL Java_TestJarr_sumAndAvg (JNIEnv *env, jobject obj, jintArray num) { jint *carr = env->GetIntArrayElements (num, NULL ); if (NULL == carr) return NULL ; jsize length = env->GetArrayLength (num); jdouble sum = 0.0 ; for (int i = 0 ; i < length; ++i) sum += carr[i]; jdouble avg = sum / length; env->ReleaseIntArrayElements (num, carr, 0 ); jdouble resArr[] = {sum, avg}; jdoubleArray outRes = env->NewDoubleArray (2 ); if (NULL == outRes) return NULL ; env->SetDoubleArrayRegion (outRes, 0 , 2 , resArr); return outRes; }
编译并运行
1 2 3 javac TestJarr.java g++ -fPIC -shared -o libmyjarr.so -I /usr/lib/jvm/java-8-oracle/include/ -I /usr/lib/jvm/java-8-oracle/include/linux/ TestJarr.cpp java -Djava.library.path=. TestJarr
运行结果:
1 2 In java, the sum is 94.0 In java, the average is 23.5
这些内容对于我当前已经够用,关于访问native访问java对象变量以及方法回调的内容请参见下面的参考文档。 以上这些例子都比较简单,实际上可以通过将复杂的计算放在C++函数中运行,最后通过native函数包裹该C++函数之后在JNI中调用,这样需要处理的内容会少很多,而且应该速度也会比较快,少去了那些过多的转换工作。
参考 1.Java Native Interface (JNI) 2.Wikipdeia-Java Native Interface 3.Oracle-JNI 4.Java programming with JNI