JNI 开发流程主要分为以下 6 步:
- 编写声明了 native 方法的 Java 类
- 将 Java 源代码编译成 class 字节码文件
- 用 javah -jni 命令生成
.h
头文件(javah 是 jdk 自带的一个命令,-jni 参数表示将 class 中用native 声明的函数生成 JNI 规则的函数) - 用本地代码实现
.h头
文件中的函数 - 将本地代码编译成动态库(Windows:\*.dll,linux/unix:\*.so,mac os x:\*.jnilib)
- 拷贝动态库至 java.library.path 本地库搜索目录下,并运行 Java 程序
通过上面的介绍,相信大家对 JNI 及开发流程有了一个整体的认识,下面通过一个 HelloWorld 的示例,再深入了解 JNI 开发的各个环节及注意事项。
HelloWorld
注意:这个案例用命令行的方式介绍开发流程,这样大家对 JNI 开发流程的印象会更加深刻。
第一步,新建一个 JniBridge.java 源文件
package com.sample.jni;
public class JniBridge {
public static String callJava(String param){
System.out.println("java:" + param);
return "receive call java";
}
public native static String callNative(String param);
}
在spring的自定义类文件静态调用
@Component
public class ApplicationStartedListener implements ApplicationListener<ApplicationStartedEvent> {
static {
System.load("/root/libJniBridge.so");
}
@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
String text = JniBridge.callNative("call native");
System.out.println("java:" + text);
}
}
第二步,用idea打出springboot对应项目的jar包
第三步,用 javah -jni 命令,根据class
字节码文件生成.h
头文件(-jni 参数是可选的)
javah -classpath ./ -jni com.sample.jni.JniBridge
第四步,用本地代码实现.h头文件中的函数
com.sample.jni.JniBridge.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com__sample_jni_JniBridge */
#ifndef _Included_com__sample_jni_JniBridge
#define _Included_com__sample_jni_JniBridge
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com__sample_jni_JniBridge
* Method: callNative
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com__sample_jni_JniBridge_callNative
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
com_sample_jni_JniBridge.c
#include "com__sample_jni_JniBridge.h"
#include <pthread.h>
pthread_t a_thread;
static JavaVM *javaVM;
static jmethodID global_mid = NULL;
static jclass global_ref = NULL;
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
printf("JNI_OnLoad startup .. \\n");
javaVM = vm;
JNIEnv *env = NULL;
jint result;
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) == JNI_OK) {
printf("Catch JNI_VERSION_1_6 \\n");
result = JNI_VERSION_1_6;
}
else if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) == JNI_OK) {
printf("Catch JNI_VERSION_1_4 \\n");
result = JNI_VERSION_1_4;
}
else {
printf("Catch JNI_VERSION_1_2 \\n");
result = JNI_VERSION_1_2;
}
jclass classLoaderClass = (*env)->FindClass(env, "com/hexin/ifind/sample/jni/JniBridge");
if (classLoaderClass) {
printf("load has find JniBridge asd\\n");
global_ref = (*env)->NewGlobalRef(env, classLoaderClass);
global_mid = (*env)->GetStaticMethodID(env, classLoaderClass,"callJava", "()Ljava/lang/String;");
if( global_mid == NULL ){
printf("load not find global_mid \\n");
}else{
printf("load find global_mid \\n");
}
}else{
printf("load not find JniBridge \\n");
}
return result;
}
void *thread_function(){
printf("thread_function is running \\n");
while(1){
sleep(3);
printf("thread_function is running \\n");
int isAttached = 0;
JNIEnv* pEnv;
if ((*javaVM)->GetEnv(javaVM,(void**)&pEnv, JNI_VERSION_1_6) == JNI_OK){
printf("JNI_VERSION is 1_6 \\n");
}
else if ((*javaVM)->GetEnv(javaVM,(void**)&pEnv, JNI_VERSION_1_4) == JNI_OK){
printf("JNI_VERSION is 1_4 \\n");
}
else if ((*javaVM)->GetEnv(javaVM,(void**)&pEnv, JNI_VERSION_1_4) == JNI_OK)
{
printf("JNI_VERSION is 1_2 \\n");
}
else if ((*javaVM)->AttachCurrentThread(javaVM,(void**)&pEnv, NULL) == JNI_OK)
{
printf("thread_function is system load \\n");
isAttached = 1;
}else{
printf("javaVM->Env Error \\n");
}
jmethodID methodId = (*pEnv)->GetStaticMethodID(pEnv,global_ref, "callJava", "()Ljava/lang/String;");
if (methodId == NULL)
{
const char msg;
(*pEnv)->ThrowNew(pEnv, global_ref,msg);
printf("thread_function is 3 \\n");
return -3;
}
const char *c_str = NULL;
char buff[128] = {0};
sprintf(buff, "Hello world%s \\n", c_str);
jstring strDataContent = (*pEnv)->NewStringUTF(pEnv, buff);
(*pEnv)->CallStaticVoidMethod(pEnv,global_ref, methodId, strDataContent);
if (isAttached)
{
(*javaVM)->DetachCurrentThread(javaVM);
}
}
}
JNIEXPORT jstring JNICALL
Java_com__sample_jni_JniBridge_callNative(JNIEnv *env, jobject j_object, jstring j_str) {
long status = (*env)->GetJavaVM(env,&javaVM);
int res;
res = pthread_create(&a_thread, NULL, thread_function, NULL);
const char *c_str = NULL;
c_str = (*env)->GetStringUTFChars(env, j_str, NULL);
printf("Native: %s \\n", c_str);
char buff[128] = {0};
sprintf(buff, "Hello world %s \\n", c_str);
return (*env)->NewStringUTF(env, buff);
}
第五步,将 C/C++ 代码编译成本地动态库文件动态库文件名命名规则:lib+动态库文件名+后缀(操作系统不一样,后缀名也不一样)如:
- Mac OS X : libJniBridge.jnilib
- Windows :libJniBridge.dll(不需要 lib 前缀)
- Linux/Unix:libJniBridge.so(案例实际) c编译生成so库
gcc -I /usr/lib/jvm/default-jvm/include -I /usr/lib/jvm/default-jvm/include/linux/ -fPIC -shared com_sample_jni_JniBridge.c -o libJniBridge.so
c++编译生成so库
g++ -I /usr/lib/jvm/default-jvm/include -I /usr/lib/jvm/default-jvm/include/linux/ -fPIC -shared JniBridge.cpp -lpthread -std=c++11 -o libJniBridge.so
过程中遇到在子线程FindClass失败
有时候会在子线程去调用Java类,但是在我们创建的子线程(通过pthread_create创建)中调用FindClass查找非系统类时会失败(查找系统类不会失败),返回值为NULL,为什么尼?这是因为通过AttachCurrentThread附加到虚拟机的线程在查找类时只会通过系统类加载器进行查找,不会通过应用类加载器进行查找,因此可以加载系统类,但是不能加载非系统类,如自己在java层定义的类会返回NULL。