🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
### 14.1 JNI的开发流程 JNI的开发流程有如下几步,首先需要在Java中声明native方法,接着用C或者C++实现native方法,然后就可以编译运行了。 * 1.在Java中声明native方法 创建一个类,这里叫做JniTest.java,代码如下所示。 package com.ryg; import java.lang.System; public class JniTest { static { System.loadLibrary("jni-test"); } public static void main(String args[]) { JniTest jniTest = new JniTest(); System.out.println(jniTest.get()); jniTest.set("hello world"); } public native String get(); public native void set(String str); } 可以看到上面的代码中,声明了两个native方法:get和set(String),这两个就是需要在JNI中实现的方法。在JniTest的头部有一个加载动态库的过程,其中jni-test是so库的标识,so库完整的名称为libjni-test.so,这是加载so库的规范。 * 2.编译Java源文件得到cIass文件,然后通过javah命令导出JNI的头文件 具体的命令如下: javac com/ryg/JniTest.java javah com.ryg.JniTest 在当前目录下,会产生一个com_ryg_JniTest.h的头文件,它是javah命令自动生成的,内容如下所示。 /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_ryg_JniTest */ #ifndef _Included_com_ryg_JniTest #define _Included_com_ryg_JniTest #ifdef __cplusplus extern "C" { #endif /* * Class: com_ryg_JniTest * Method: get * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_ryg_JniTest_get (JNIEnv *, jobject); /* * Class: com_ryg_JniTest * Method: set * Signature: (Ljava/lang/String; )V */ JNIEXPORT void JNICALL Java_com_ryg_JniTest_set (JNIEnv *, jobject, jstring); #ifdef __cplusplus } #endif #endif 上面的代码需要做一下说明,首先函数名的格式遵循如下规则:Java_包名_类名_方法名。比如JniTest中的set方法,到这里就变成了JNIEXPORT void JNICALL Java_com_ryg_JniTest_set(JNIEnv *, jobject, jstring),其中com_ryg是包名,JniTest是类名,jstring是代表的是set方法的String类型的参数。关于Java和JNI的数据类型之间的对应关系会在14.3节中进行介绍,这里只需要知道Java的String对应于JNI的jstring即可。JNIEXPORT、JNICALL、JNIEnv和jobject都是JNI标准中所定义的类型或者宏,它们的含义如下: * · JNIEnv*:表示一个指向JNI环境的指针,可以通过它来访问JNI提供的接口方法; * · jobject:表示Java对象中的this; * · JNIEXPORT和JNICALL:它们是JNI中所定义的宏,可以在jni.h这个头文件中查找到。 下面的宏定义是必需的,它指定extern "C"内部的函数采用C语言的命名风格来编译。否则当JNI采用C++来实现时,由于C和C++编译过程中对函数的命名风格不同,这将导致JNI在链接时无法根据函数名查找到具体的函数,那么JNI调用就无法完成。更多的细节实际上是有关C和C++编译时的一些问题,这里就不再展开了。 #ifdef __cplusplus extern "C" { #endif * 3.实现JNI方法 JNI方法是指Java中声明的native方法,这里可以选择用C++或者C来实现,它们的实现过程是类似的,只有少量的区别,下面分别用C++和C来实现JNI方法。首先,在工程的主目录下创建一个子目录,名称随意,这里选择jni作为子目录的名称,然后将之前通过javah生成的头文件com_ryg_JniTest.h复制到jni目录下,接着创建test.cpp和test.c两个文件,它们的实现如下所示。 // test.cpp #include "com_ryg_JniTest.h" #include <stdio.h> JNIEXPORT jstring JNICALL Java_com_ryg_JniTest_get(JNIEnv *env, jobject thiz) { printf("invoke get in c++\n"); return env->NewStringUTF("Hello from JNI ! "); } JNIEXPORT void JNICALL Java_com_ryg_JniTest_set(JNIEnv *env, jobject thiz, jstring string) { printf("invoke set from C++\n"); char* str = (char*)env->GetStringUTFChars(string, NULL); printf("%s\n", str); env->ReleaseStringUTFChars(string, str); } // test.c #include "com_ryg_JniTest.h" #include <stdio.h> JNIEXPORT jstring JNICALL Java_com_ryg_JniTest_get(JNIEnv *env, jobject thiz) { printf("invoke get from C\n"); return (*env)->NewStringUTF(env, "Hello from JNI ! "); } JNIEXPORT void JNICALL Java_com_ryg_JniTest_set(JNIEnv *env, jobject thiz, jstring string) { printf("invoke set from C\n"); char* str = (char*)(*env)->GetStringUTFChars(env, string, NULL); printf("%s\n", str); (*env)->ReleaseStringUTFChars(env, string, str); } 可以发现,test.cpp和test.c的实现很类似,但是它们对env的操作方式有所不同,因此用C++和C来实现同一个JNI方法,它们的区别主要集中在对env的操作上,其他都是类似的,如下所示。 C++:env->NewStringUTF("Hello from JNI ! "); C: (*env)->NewStringUTF(env, "Hello from JNI ! ") * 4.编译so库并在Java中调用 so库的编译这里采用gcc,切换到jni目录中,对于test.cpp和test.c来说,它们的编译指令如下所示。 C++:gcc -shared -I /usr/lib/jvm/java-7-openjdk-amd64/include -fPIC test.cpp -o libjni-test.so C: gcc -shared -I /usr/lib/jvm/java-7-openjdk-amd64/include -fPIC test.c -o libjni-test.so 上面的编译命令中,/usr/lib/jvm/java-7-openjdk-amd64是本地的jdk的安装路径,在其他环境编译时将其指向本机的jdk路径即可。而libjni-test.so则是生成的so库的名字,在Java中可以通过如下方式加载:System.loadLibrary("jni-test"),其中so库名字中的“lib”和“.so”是不需要明确指出的。so库编译完成后,就可以在Java程序中调用so库了,这里通过Java指令来执行Java程序,切换到主目录,执行如下指令:java -Djava.library.path=jni com.ryg.JniTest,其中-Djava.library.path=jni指明了so库的路径。 首先,采用C++产生so库,程序运行后产生的日志如下所示。 invoke get in c++ Hello from JNI ! invoke set from C++ hello world 然后,采用C产生so库,程序运行后产生的日志如下所示。 invoke get from C Hello from JNI ! invoke set from C hello world 通过上面的日志可以发现,在Java中成功地调用了C/C++的代码,这就是JNI典型的工作流程。