java JNI介紹

java JNI介紹

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程式碼和其他程式語言的程式碼相互調用。

今天就主要記錄下日常的一些使用方式

1、 Java調用C++程式碼

  1. 首先創建我們的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 + '\'' +
                        '}';
            }
        }
    
    }
    
    
  2. 編譯成class文件

    javac com/wbo112/jni/JNIDemo.java
    
  3. 生成頭文件

    javah com.wbo112.jni.JNIDemo
    

    這時就會在當前目錄下生成com_wbo112_jni_JNIDemo.h。

  4. 編寫對應的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文檔這裡有介紹://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;
    
    }
    
    ////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);
    
    	////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) {   		
    			////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;
    	
    	//通過對象獲取對應的類
    	////docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetObjectClass
    	jclass jcls=env->GetObjectClass(jobj);
    
    	//另一種方式獲取對應的類
    	////docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#FindClass
    	jclass jentrycls=env->FindClass("com/wbo112/jni/JNIDemo$Entry");
    
    	//獲取無參的構造方法
    	////docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetMethodID
    	jmethodID  jinit=env->GetMethodID(jcls,"<init>", "()V");
    
    	//調用構造方法獲取對象
    	////docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#NewObject
    	jobject jentryobj=env->NewObject(jentrycls,jinit);
    
    	//獲取entry類的str欄位
    	////docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetFieldID
    	jfieldID jfieldStr=env->GetFieldID(jentrycls,"str","Ljava/lang/String;");
    
    	//給str欄位設置值
    	////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);
    	
    	//獲取方法
    	////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");
    	
    	//調用方法
    	////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);
    
    	//釋放全局對象
    	////docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#DeleteGlobalRef
    	env->DeleteGlobalRef(jobj);
    	env->DeleteGlobalRef(jstr);
    
    	//當前執行緒和jvm進行分離
    	////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對象是不能跨執行緒傳遞的,需要先轉成全局引用
    	////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
    
  5. 執行java程式

    java -Djava.library.path=./ com.wbo112.jni.JNIDemo
    

2、C++程式碼調用java程式碼

這個完全就是Oracle官方的例子了

The Invocation API (oracle.com)

  1. 首先創建個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;
    	}
    
    
  2. 再創建c++需要調用的java文件 Main.java

    public class Main {
        public static void test(int a) {
            System.out.println(" Main test:" + a);
        }
    }
    
    
  3. 編譯ctj.cpp文件

    g++ -I $JAVA_HOME/include/linux -I $JAVA_HOME/include   -L"$JAVA_HOME/jre/lib/amd64/server/" ctj.cpp -o  ctj   -ljvm
    
  4. 執行生成的ctj文件

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/amd64/server/
    ./ctj
    
Tags: