JNI是Java Native Interface的全称。
oracle文档中是这样描述的
The JNI is a native programming interface. It allows Java code that runs inside a Java Virtual Machine (VM) to interoperate with applications and libraries written in other programming languages, such as C, C++, and assembly.
翻译过来就是 JNI 是本机编程接口。 它允许在 Java 虚拟机 (VM) 内运行的 Java 代码与使用其他编程语言(例如 C、C++ 和汇编)编写的应用程序和库进行互操作。
说白了就是java代码和其他编程语言的代码相互调用。
今天就主要记录下日常的一些使用方式
首先创建我们的java类 JNIDemo.java
package com.wbo112.jni; import java.util.*; import java.util.concurrent.*; public class JNIDemo { public native String callHello(); public native boolean sendMsg(String str); //保存JNI返回的结果 private BlockingQueue<Entry> queue = new ArrayBlockingQueue<Entry>(10); //处理jni返回的任务 线程池 private ExecutorService executor = Executors.newSingleThreadExecutor(); public static void main(String[] args) throws InterruptedException { //加载so文件 libJNIDemo.so System.loadLibrary("JNIDemo"); //System.load(); 也可以用这种方式,参数是so文件的全路径 JNIDemo jniDemo = new JNIDemo(); //保存需要提交的任务 Set<String> sets = new ConcurrentSkipListSet<>(); String str; for (int i = 0; i < 10; i++) { str = UUID.randomUUID().toString(); sets.add(str); System.out.println("commit task :" + str); //在这里会调用jni 提交任务,jni中会新启动一个线程,异步去执行任务,执行完了会调用putEntry方法,添加到需要回调的任务列表中 jniDemo.sendStr(str); } Thread thread = new Thread(() -> { while (true) { try { Entry entry = jniDemo.queue.take(); sets.remove(entry.str); //在线程中中,进行回调通知,比如向调用方发送任务处理结果 jniDemo.executor.execute(() -> System.out.println(entry.str + "process finish")); //所有任务进行回调后,结束添加回调任务线程,同时关闭线程池 if (sets.isEmpty()) { jniDemo.executor.shutdown(); break; } } catch (InterruptedException e) { System.out.println("thread interrupt "); jniDemo.executor.shutdown(); break; } } }); thread.start(); //也可以通过这种方式,比如在其他地方在中断回调任务的添加执行,关闭线程池 //thread.interrupt(); } public boolean sendStr(String str) { return sendMsg(str); } public void putEntry(Entry entry) { queue.add(entry); } private static class Entry { //表示一个任务 private String str; //表示任务处理结果 private String result; public String getStr() { return str; } public void setStr(String str) { this.str = str; } public String getResult() { return result; } public void setResult(String result) { this.result = result; } @Override public String toString() { return "Entry{" + "str='" + str + '\'' + ", result='" + result + '\'' + '}'; } } }
编译成class文件
javac com/wbo112/jni/JNIDemo.java
生成头文件
javah com.wbo112.jni.JNIDemo
这时就会在当前目录下生成com_wbo112_jni_JNIDemo.h。
编写对应的cpp文件 JNIDemo1.cpp
#include "com_wbo112_jni_JNIDemo.h" #include <iostream> #include <thread> #include <cstdlib> #include <ctime> #include <unistd.h> JavaVM *vm=NULL; unsigned seed; //在java加载so文件的时候,就会调用到这个JNI_OnLoad //oracle文档这里有介绍:https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNJI_OnLoad jint JNI_OnLoad(JavaVM *jvm, void *reserved){ //这个表示java虚拟机,这个需要保存下来,因为JNIEnv只是当前线程有效,如果要在其他线程获取JNIEnv,就需要通过这个jvm来获取,后面有相应代码 vm=jvm; //这个是为了模拟后面的回调时间,业务中应该不关注 seed = time(0); srand(seed); return JNI_VERSION_1_8; } //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNI_OnUnload //so文件卸载的时候,会执行这个函数,在这里面可以做一些清理工作 void JNI_OnUnload_L(JavaVM *vm, void *reserved){ vm=NULL; } void threadfunc(jobject jobj,jstring jstr) { JNIEnv* env = NULL; sleep(rand() % 10); //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#GetEnv //这里有介绍,如果不是当前线程,是获取不到env的,这时,返回值就是JNI_EDETACHED,这时就需要调用AttachCurrentThread,给当前线程绑定env jint status = vm->GetEnv((void **)&env, JNI_VERSION_1_8); if (status == JNI_EDETACHED) { //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#AttachCurrentThread if (vm->AttachCurrentThread((void **)&env, NULL)!= JNI_OK){ return; } } else if(status!= JNI_OK){ std::cout<<"getEnv err"<<std::endl; return; } //这个是将jstring转成char* const char *str = env->GetStringUTFChars(jstr, 0); std::cout << "start process task " + (std::string)str << std::endl; //通过对象获取对应的类 //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetObjectClass jclass jcls=env->GetObjectClass(jobj); //另一种方式获取对应的类 //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#FindClass jclass jentrycls=env->FindClass("com/wbo112/jni/JNIDemo$Entry"); //获取无参的构造方法 //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetMethodID jmethodID jinit=env->GetMethodID(jcls,"<init>", "()V"); //调用构造方法获取对象 //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#NewObject jobject jentryobj=env->NewObject(jentrycls,jinit); //获取entry类的str字段 //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetFieldID jfieldID jfieldStr=env->GetFieldID(jentrycls,"str","Ljava/lang/String;"); //给str字段设置值 //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#Set_type_Field_routines env->SetObjectField(jentryobj,jfieldStr,jstr); char* msg = "Hello World!"; jstring result = env->NewStringUTF(msg); jfieldID jfieldResult=env->GetFieldID(jentrycls,"result","Ljava/lang/String;"); env->SetObjectField(jentryobj,jfieldResult,result); //获取方法 //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetMethodID jmethodID jmtd=env->GetMethodID(jcls,"putEntry", "(Lcom/wbo112/jni/JNIDemo$Entry;)V"); //调用方法 //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#Call_type_Method_routines env->CallVoidMethod(jobj,jmtd,jentryobj); std::cout<<" end process task " + (std::string)str<<std::endl; //释放前面构造的char* env->ReleaseStringUTFChars( jstr, str); //释放全局对象 //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#DeleteGlobalRef env->DeleteGlobalRef(jobj); env->DeleteGlobalRef(jstr); //当前线程和jvm进行分离 //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#DetachCurrentThread vm->DetachCurrentThread(); } JNIEXPORT jstring JNICALL Java_com_wbo112_jni_JNIDemo_callHello (JNIEnv *env, jobject jobj){ char* msg = "Hello World!"; jstring result = env->NewStringUTF(msg); // C style string to Java String return result; } JNIEXPORT jboolean JNICALL Java_com_wbo112_jni_JNIDemo_sendMsg (JNIEnv *env, jobject jobj, jstring jstr){ //jobject对象是不能跨线程传递的,需要先转成全局引用 //https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#NewGlobalRef jobject globalJobj=env->NewGlobalRef(jobj); jobject globalJstr=env->NewGlobalRef(jstr); //这里使用的是std::thread ,所以编译的时候需要加参数-std=c++11 std::thread t1(threadfunc,globalJobj,(jstring)globalJstr); //t1.join(); t1.detach(); return 1; }
编译cpp文件
g++ -shared -std=c++11 -I $JAVA_HOME/include/linux -I $JAVA_HOME/include -fPIC -o libJNIDemo.so JNIDemo1.cpp
执行java程序
java -Djava.library.path=./ com.wbo112.jni.JNIDemo
这个完全就是Oracle官方的例子了
The Invocation API (oracle.com)
首先创建个ctj.cpp文件
#include <jni.h> /* where everything is defined */ 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=./"; vm_args.version = JNI_VERSION_1_8; 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("Main"); jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V"); env->CallStaticVoidMethod(cls, mid, 100); /* We are done. */ jvm->DestroyJavaVM(); return 0; }
再创建c++需要调用的java文件 Main.java
public class Main { public static void test(int a) { System.out.println(" Main test:" + a); } }
编译ctj.cpp文件
g++ -I $JAVA_HOME/include/linux -I $JAVA_HOME/include -L"$JAVA_HOME/jre/lib/amd64/server/" ctj.cpp -o ctj -ljvm
执行生成的ctj文件
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/amd64/server/ ./ctj