已经在工作中碰到了两个这样的例子了,项目用java,但需要调用C/C++的库。之前一个是用java写hadoop的job,但是调用的算法是用C++实现的,使用的是jni。现在好像jni在android上面也有用到,但是我不了解。我自己也不懂java,但实际工作中有的时候也要看看java代码。所以决定写一个简单的jni例子,了解一下jni怎么完成C/C++调用的。
对于jni的内部实现,我也是不求甚解。编写一个java程序
jni调用的java代码 view raw 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Sample1 { public native int intMethod (int n) ; public native boolean booleanMethod (boolean bool) ; public native String stringMethod (String text) ; public native int intArrayMethod (int [] intArray) ; public static void main (String[] args) { System.loadLibrary("Sample1" ); Sample1 sample = new Sample1(); int square = sample.intMethod(3 ); System.out.println("intMethod: " + square); boolean bool = sample.booleanMethod(true ); System.out.println("booleanMethod: " + bool); String text = sample.stringMethod("java" ); System.out.println("stringMethod: " + text); int sum = sample.intArrayMethod(new int [] { 1 , 1 , 2 , 3 , 5 , 8 , 13 }); System.out.println("intArrayMethod: " + sum); } }
然后编译java文件
编译完以后, 使用下面的命令生成jni调用的头文件
1 javah -classpath ./ -jni Sample1
生成的头文件为Sample1.h, 就是下面C/C++实现的函数的声明, 注意:Sample1.h是自动生成的
jni生成的头文件 view raw 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 36 37 38 39 40 41 42 43 44 45 #include <jni.h> #ifndef _Included_Sample1 #define _Included_Sample1 #ifdef __cplusplus extern "C" {#endif * Class: Sample1 * Method: intMethod * Signature: (I)I */ JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *, jobject, jint) ; * Class: Sample1 * Method: booleanMethod * Signature: (Z)Z */ JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod (JNIEnv *, jobject, jboolean) ; * Class: Sample1 * Method: stringMethod * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_Sample1_stringMethod (JNIEnv *, jobject, jstring) ; * Class: Sample1 * Method: intArrayMethod * Signature: ([I)I */ JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod (JNIEnv *, jobject, jintArray) ;#ifdef __cplusplus } #endif #endif
有了头文件后,就可以实现这些调用的C/C++代码
C++实现的Sample1.cpp view raw 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 36 37 38 39 #include "Sample1.h" #include <string.h> #include <ctype.h> JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *env, jobject obj, jint num) { return num * num; } JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod (JNIEnv *env, jobject obj, jboolean boolean) { return !boolean; } JNIEXPORT jstring JNICALL Java_Sample1_stringMethod (JNIEnv *env, jobject obj, jstring jstr) { const char *str = env->GetStringUTFChars(jstr, 0 ); char cap[128 ]; strcpy (cap, str); env->ReleaseStringUTFChars(jstr, str); char * ptr = cap; while (*ptr != '\0' ){ *ptr = toupper (*ptr); ptr++; } return env->NewStringUTF(cap); } JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod (JNIEnv *env, jobject obj, jintArray array ) { int i, sum = 0 ; jsize len = env->GetArrayLength(array ); jint *body = env->GetIntArrayElements(array , 0 ); for (i=0 ; i<len; i++) { sum += body[i]; } env->ReleaseIntArrayElements(array , body, 0 ); return sum; }
然后调用下面的命令生成动态链接库(系统:ubuntu 12.04,gcc编译)
1 g++ -I /usr/lib/jvm/java-1.6 .0 -openjdk/include/ -I /usr/lib/jvm/java-1.6 .0 -openjdk/include/linux/ Sample1.cpp -f PIC -shared -o libSample1.so
具体的jdk的路径应该是和本地的相适应的。
然后运行java程序
运行结果如下
图中加入了一个环境变量的声明,export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
, 当java加载动态链接库时,会在本地路径下找libSample1.so。
上面是调用C++的代码,C实现的代码如下
C语言实现的Sample1.c view raw 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 36 37 38 39 40 41 42 43 44 45 46 * the difference between C++ and c like this: * const char *str = (*env)->GetStringUTFChars(env, jstr, 0); * const char *str = env->GetStringUTFChars(jstr, 0); */ #include "Sample1.h" #include <string.h> #include <ctype.h> JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *env, jobject obj, jint num) { return num * num; } JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod (JNIEnv *env, jobject obj, jboolean boolean) { return !boolean; } JNIEXPORT jstring JNICALL Java_Sample1_stringMethod (JNIEnv *env, jobject obj, jstring jstr) { const char *str = (*env)->GetStringUTFChars(env, jstr, 0 ); char cap[128 ]; strcpy (cap, str); (*env)->ReleaseStringUTFChars(env, jstr, str); char * ptr = cap; while (*ptr != '\0' ){ *ptr = toupper (*ptr); ptr++; } return (*env)->NewStringUTF(env, cap); } JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod (JNIEnv *env, jobject obj, jintArray array ) { int i, sum = 0 ; jsize len = (*env)->GetArrayLength(env, array ); jint *body = (*env)->GetIntArrayElements(env, array , 0 ); for (i=0 ; i<len; i++) { sum += body[i]; } (*env)->ReleaseIntArrayElements(env, array , body, 0 ); return sum; }
C与C++的实现基本一样,唯一的差异在于用来访问 JNI 函数的方法。在 C 中,JNI 函数调用由“(*env)->”作前缀,目的是为了取出函数指针所引用的值。在 C++ 中,JNIEnv 类拥有处理函数指针查找的内联成员函数。下面将说明这个细微的差异,其中,这两行代码访问同一函数,但每种语言都有各自的语法。
1 2 C 语法:jsize len = (*env)->GetArrayLength(env,array ); C++ 语法:jsize len = env->GetArrayLength(array );
编译命令
1 gcc -I /usr/lib/jvm/java-1.6 .0 -openjdk/include/ -I /usr/lib/jvm/java-1.6 .0 -openjdk/include/linux/ Sample1.c -f PIC -shared -o libSample1.so
运行结果与上面C++的输出是一致的。
实际上我是用了一个脚本完成编译等工作的
完成整个过程的脚本 view raw 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 #!/bin/bash echo "### compile Sample1.java" javac Sample1.java echo "### generate jni headerfile Sample1.h" javah -classpath ./ -jni Sample1 echo "### compile cpp Sample1.so" g++ -I /usr/lib/jvm/java-1.6 .0 -openjdk/include/ -I /usr/lib/jvm/java-1.6 .0 -openjdk/include/linux/ Sample1.cpp -f PIC -shared -o libSample1.so export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH echo "### run cpp" echo "---------------" java Sample1 echo "--------------- end" rm libSample1.so echo "### compile c Sample1.so" gcc -I /usr/lib/jvm/java-1.6 .0 -openjdk/include/ -I /usr/lib/jvm/java-1.6 .0 -openjdk/include/linux/ Sample1.c -f PIC -shared -o libSample1.so echo "### run c" echo "---------------" java Sample1
脚本的运行结果如下图
github上本文的例子代码 jni调用的例子
本文参考了以下两个blog的内容, 代码来自第二个blog。