Java代码 [JNITest.java]:
package darcy; public class JNITest { static{ System.loadLibrary("Hello"); } public native void HelloKitty(); public static void main(String[] args){ new JNITest().HelloKitty(); } }
编译Class文件,然后使用JDK提供的javah工具生成c++层的头文件,当然也可以手写.
javah -jni darcy.JNITest
运行后生成的头文件内容[darcy_JNITest.h]:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class darcy_JNITest */ #ifndef _Included_darcy_JNITest #define _Included_darcy_JNITest #ifdef __cplusplus extern "C" { #endif /* * Class: darcy_JNITest * Method: HelloKitty * Signature: ()V */ JNIEXPORT void JNICALL Java_darcy_JNITest_HelloKitty (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
这里我们可以看到,Java层的HelloKitty()方法就对应上C++的Java_darcy_JNITest_HelloKitty,从这里我们可以看出Java层对应的C++层方法签名是Java_为前缀+全限定方法名。
实现这个方法 [darcy_JNITest.cpp]:
#include <jni.h> #include "darcy_JNITest.h" #include <stdio.h> JNIEXPORT void JNICALL Java_darcy_JNITest_HelloKitty (JNIEnv *, jobject){ printf("hello kitty\n"); }
下面使用g++编译生成动态链接库libHello.so。对应上Java层调用的System.loadLibrary("Hello");
从实现的c/c++代码上看到,有个jni.h的头文件需要引进来。该文件位于jdk自带的include文件夹里面,如我的在:/usr/java/jdk1.7.0_45/include这个下面.
这里需要注意的是jni.h里面需要依赖于一个叫做jni_md.h的文件,但是可能不再在目录下,向我的在/usr/java/jdk1.7.0_45/include/linux下面,如果报相关的编译错误,
不妨到这里面找一下。
运行命令,生成.so文件:
g++ -shared -fPIC -I /usr/java/jdk1.7.0_45/include darcy_JNITest.cpp -o libHello.so
这里要说明一下关于参数(-fPIC): 这是由于我在编译的时候出现了下面的错误:
/usr/local/bin/ld: /tmp/ccdq1TYq.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
/tmp/ccdq1TYq.o: error adding symbols: 错误的值
所以才加上去的,不知道是不是跟之前为了编译linux内核而升级了ld这个东西有关,大家在调用该命令的时候可以先把-fPIC去掉试一下。
好了,万事具备,我们运行一下Java程序看看:
java -Djava.library.path=. darcy.JNITest //在=的.表示当前路径
output:hello kitty
文件的路径图:
Java代码[JNIInvoke.Java]:
package darcy; public class JNIInvoke{ public static String invoke(){ System.out.println("This is from Java!"); return "Haha"; } public static void main(String[] args){ System.out.println(invoke()); } }
我们将要在c++层调用的是invoke()方法,main只是用来测试.
第一步是编译代码,生成JNIInvoke.class。
C++代码[JavaInvoke.cpp]:
#include <iostream> #include <jni.h> #include <stdlib.h> #include <string.h> using namespace std; string JStringToCString (JNIEnv *env, jstring str); char* jstringTostring(JNIEnv* env, jstring jstr); int main(){ JavaVM *jvm; /* denotes a Java VM */ JNIEnv *env; /* pointer to native method interface */ JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */ JavaVMOption* options = new JavaVMOption[1]; options[0].optionString = "-Djava.class.path=.:/usr/java/jdk1.7.0_45/lib"; vm_args.version = JNI_VERSION_1_6; vm_args.nOptions = 1; vm_args.options = options; vm_args.ignoreUnrecognized = false; /* load and initialize a Java VM, return a JNI interface * pointer in env */ JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); delete options; /* invoke the Main.test method using the JNI */ jclass cls = env->FindClass("darcy/JNIInvoke"); jmethodID mid = env->GetStaticMethodID(cls, "invoke", "()Ljava/lang/String;"); jstring result = (jstring)env->CallStaticObjectMethod(cls, mid); cout<< jstringTostring(env,result) << endl; /* We are done. */ jvm->DestroyJavaVM(); return 0; } //把jstring类型转化成c字符串,可不关注该方法 char* jstringTostring(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = env->FindClass("java/lang/String"); jstring strencode = env->NewStringUTF("utf-8"); jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode); jsize alen = env->GetArrayLength(barr); jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE); if (alen > 0) { rtn = (char*)malloc(alen + 1); memcpy(rtn, ba, alen); rtn[alen] = 0; } env->ReleaseByteArrayElements(barr, ba, 0); return rtn; }
这里有个方法需要注意一下: GetStaticMethodID(), 其中最后一个参数是方法签名(Signature),可以通过javap -s darcy/JNIInvoke看到
接下来就是编译C++代码了。
由上面我们可以看到引进了头文件jni.h,同样的也是在/usr/java/jdk1.7.0_45/include/目录下
编译指令:
g++ -g -I/usr/java/jdk1.7.0_45/include/ -L/usr/java/jdk1.7.0_45/jre/lib/amd64/server JavaInvoke.cpp -ljvm
这里用 -g是为了方便gdb的调试,因为JNI的调用出错之后很难定位错误,所以最后在怀疑出错的地方单布调试一下。-L后面是编译需要用到的库的路径。因为在这里要用到libjvm.so这个库。-ljvm就是定位的库了。如果忘记加进来的话,编译过程会报错:
:对‘JNI_CreateJavaVM’未定义的引用
最后,为了运行时能找到libjvm.so这个库,要在环境变量中设置参数:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/java/jdk1.7.0_45/jre/lib/amd64/server
运行: ./a.out
output:
This is from Java! //Java层的打印
Haha //C/C++层的打印
文件的路径图: