摘要:
java程序可以通过jni调用C或者C++的库。本文实现了一个简单的jni调用例子。
已经在工作中碰到了两个这样的例子了,项目用java,但需要调用C/C++的库。之前一个是用java写hadoop的job,但是调用的算法是用C++实现的,使用的是jni。现在好像jni在android上面也有用到,但是我不了解。我自己也不懂java,但实际工作中有的时候也要看看java代码。所以决定写一个简单的jni例子,了解一下jni怎么完成C/C++调用的。
对于jni的内部实现,我也是不求甚解。编写一个java程序
jni调用的java代码 (Sample1.java) download
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文件
javac Sample1.java
编译完以后, 使用下面的命令生成jni调用的头文件
javah -classpath ./ -jni Sample1
生成的头文件为Sample1.h, 就是下面C/C++实现的函数的声明, 注意:Sample1.h是自动生成的
jni生成的头文件 (Sample1.h) download
/* DO NOT EDIT THIS FILE - it is machine generated */#include /* Header for class Sample1 */#ifndef _Included_Sample1#define _Included_Sample1#ifdef __cplusplusextern "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 (Sample1.cpp) download
#include "Sample1.h"#include #include JNIEXPORT jint JNICALLJava_Sample1_intMethod(JNIEnv *env, jobject obj, jint num){return num * num;}JNIEXPORT jboolean JNICALLJava_Sample1_booleanMethod(JNIEnv *env, jobject obj, jboolean boolean){return !boolean;}JNIEXPORT jstring JNICALLJava_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 JNICALLJava_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; iReleaseIntArrayElements(array, body, 0);return sum;}
然后调用下面的命令生成动态链接库(系统:ubuntu 12.04,gcc编译)
g++ -I /usr/lib/jvm/java-1.6.0-openjdk/include/ -I /usr/lib/jvm/java-1.6.0-openjdk/include/linux/ Sample1.cpp -fPIC -shared -o libSample1.so
具体的jdk的路径应该是和本地的相适应的。
然后运行java程序
java Sample1
运行结果如下
图中加入了一个环境变量的声明,export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH, 当java加载动态链接库时,会在本地路径下找libSample1.so。
上面是调用C++的代码,C实现的代码如下
C语言实现的Sample1.c (Sample1.c) download
/** 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 #include JNIEXPORT jint JNICALLJava_Sample1_intMethod(JNIEnv *env, jobject obj, jint num){return num * num;}JNIEXPORT jboolean JNICALLJava_Sample1_booleanMethod(JNIEnv *env, jobject obj, jboolean boolean){return !boolean;}JNIEXPORT jstring JNICALLJava_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 JNICALLJava_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; iReleaseIntArrayElements(env, array, body, 0);return sum;}
C与C++的实现基本一样,唯一的差异在于用来访问 JNI 函数的方法。在 C 中,JNI 函数调用由“(*env)->”作前缀,目的是为了取出函数指针所引用的值。在 C++ 中,JNIEnv 类拥有处理函数指针查找的内联成员函数。下面将说明这个细微的差异,其中,这两行代码访问同一函数,但每种语言都有各自的语法。
C 语法:jsize len = (*env)->GetArrayLength(env,array);C++ 语法:jsize len = env->GetArrayLength(array);
编译命令
gcc -I /usr/lib/jvm/java-1.6.0-openjdk/include/ -I /usr/lib/jvm/java-1.6.0-openjdk/include/linux/ Sample1.c -fPIC -shared -o libSample1.so
运行结果与上面C++的输出是一致的。
实际上我是用了一个脚本完成编译等工作的
完成整个过程的脚本 (run.sh) download
#!/bin/bashecho "### compile Sample1.java"javac Sample1.javaecho "### generate jni headerfile Sample1.h"javah -classpath ./ -jni Sample1echo "### 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 -fPIC -shared -o libSample1.soexportLD_LIBRARY_PATH=.:$LD_LIBRARY_PATHecho "### run cpp"echo "---------------"java Sample1echo "--------------- end"rm libSample1.soecho "### 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 -fPIC -shared -o libSample1.soecho "### run c"echo "---------------"java Sample1#rm libSample1.so Sample1.class Sample1.h
脚本的运行结果如下图
github上本文的例子代码 jni调用的例子
本文参考了以下两个blog的内容, 代码来自第二个blog。