JNI轉換通俗易懂的總結(Java調用C++篇)

相對C++調用java而言,使用JNI實現Java調用C++相對比較簡單點,因爲不用自己啓動和管理一個JVM。

最簡單的流程:

1.      編寫一份簡單的HelloWorld.java

class HelloWorld {

public native void displayHelloWorld();

 

static {

System.loadLibrary("hello");

}

 

public static void main(String[] args) {

new HelloWorld().displayHelloWorld();

}

}

 

2. 執行javah –jni HelloWorld生成一份C++的頭文件HelloWorld.h

/* DO NOT EDIT THIS FILE - it is machinegenerated */

#include <jni.h>

/* Header for class HelloWorld */

 

#ifndef _Included_HelloWorld

#define _Included_HelloWorld

#ifdef __cplusplus

extern "C" {

#endif

/*

 *Class:     HelloWorld

 *Method:    displayHelloWorld

 *Signature: ()V

 */

JNIEXPORT void JNICALLJava_HelloWorld_displayHelloWorld

 (JNIEnv *, jobject);

 

#ifdef __cplusplus

}

#endif

#endif

 

3.根據已經生成的頭文件,編寫一份.cpp文件HelloWorld.cpp(其中函數名跟.h文件中自動生成的保持一致),並且要include  jni.h文件,(該文件可以在%JAVA_HOME%/include文件夾下面找到)文件引入,因爲在程序中的JNIEnvjobject等類型都是在該頭文件中定義的

#include <jni.h>

#include "HelloWorld.h"

#include <stdio.h>

JNIEXPORT void JNICALLJava_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj){

         printf("Helloworld!\n");

         return;

}

 

4. 對C++文件進行編譯生成目標文件HelloWorld.o:g++ -c -fPICHelloWorld.cpp    生成HelloWorld.o

 

 

5.生成動態鏈接文件libhello.so: g++ -fpic -shared -oHelloWorld.so libhello.o 

--千萬要注意.so文件的命名規則,一定要以lib+hello+.so的格式,其中hello是必須跟System.loadLibrary("hello");中的hello保持一致。尤其是lib作爲前綴千萬不能忘記。

 

6. 要把生成的libhello.so拷貝到java.library.path目錄下,這個目錄不知怎麼在linux查詢,有個笨辦法就是寫一個簡單java程序,執行System.out.println(System.getProperty("java.library.path"));就可以得到目錄路徑。或者可以export  LD_LIBRARY_PATH=(libhello.so所在的目錄)

 

7.執行javaHelloWorld,正常輸出爲:

Hello world!

--注意第6步,如果沒有很好解決java.library.path路徑問題的話,執行HelloWorld時就會報錯:

Exception in thread "main"java.lang.UnsatisfiedLinkError: no hello in java.library.path

       at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)

       at java.lang.Runtime.loadLibrary0(Runtime.java:870)

       at java.lang.System.loadLibrary(System.java:1122)

       at HelloWorld.<clinit>(HelloWorld.java:5)

 

8. 靜態註冊和動態註冊

在java調用C++的過程中,有一個很重要的步驟就是:java運行時如何去查找到某個native方法,這就涉及到native方法要註冊到JVM中,這就分成了動態(手工)註冊和靜態(自動)註冊的概念了,爲什麼上述流程沒有看到這一步呢?因爲我們偷懶,選擇了靜態(自動)註冊這一方式。我們用javah xxxx去生成.h文件,這樣native的函數名就按標準格式生成了,這樣JVM在加載動態庫後就可以根據標準格式去找到對應的native函數調用。至於動態註冊的過程後續再補充。

補充:

動態註冊的流程

動態註冊和靜態註冊的區別就是動態註冊不需要使用javah生成JNI標準規範的.h頭文件,可以根據自己的需要去編寫C++的頭文件和源文件,函數名也可以自己定義,比如HelloWorld.c(這裏爲了簡單,用C語言的文件,其實也可以使用C++)

#include <stdlib.h>

#include <string.h>

#include <stdio.h>

#include <jni.h>

#include <assert.h>

/*自己定義函數名,而非javah所生成的複雜的標準的函數名*/

JNIEXPORT jstring JNICALL native_hello(JNIEnv *env, jclass clazz)

{

    printf("hello in c native code./n");

    return (*env)->NewStringUTF(env, "hello world returned.");

}

 

// 指定要註冊的類,把

#define JNIREG_CLASS "HelloWorld"

// 定義一個JNINativeMethod數組,其中的成員就是Java代碼中對應的native方法,下面代碼其實使用了構造函數初始化了JNINativeMethod數組中的第一個對象

static JNINativeMethodgMethods[] = {

    { "hello", "()Ljava/lang/String;", (void*)native_hello},

};

其中JNINativeMethod是一個struct結構體:

typedefstruct {

    constchar* name;

    constchar* signature;

    void*       fnPtr;

} JNINativeMethod;

 

//調用JNIEnvRegisterNatives來真正實現本地函數名和java方法名的映射關係註冊到JVM

staticint registerNativeMethods(JNIEnv* env, constchar* className,

JNINativeMethod* gMethods, int numMethods) {

    jclass clazz;

    clazz =(*env)->FindClass(env, className);

    if (clazz == NULL) {

        returnJNI_FALSE;

    }

    if ((*env)->RegisterNatives(env, clazz,gMethods, numMethods) < 0) {

        returnJNI_FALSE;

    }

    returnJNI_TRUE;

}

 

//實現JNI_OnLoad函數

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {

    JNIEnv* env = NULL;

    jint result = -1;

 

    if ((*vm)-> GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {

        return -1;

    }

    assert(env != NULL);

 

    if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,sizeof(gMethods) / sizeof(gMethods[0]))) { //註冊

        return -1;

    }

    result = JNI_VERSION_1_4;

 

    return result;

 

}

 

--代碼流程並不複雜,簡單來說就是當java代碼中調用System.loadLibraryhello.so)時,JVM會先去找到JNI_OnLoad函數,在該該函數中調用了registerNativeMethods(env,JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))---à(*env)->RegisterNatives(env, clazz, gMethods,numMethods),最終調到了RegisterNatives真正把本地函數名和java方法名的映射關係註冊到JVM中,後續java調用這些本地方法時就能根據映射關係找到。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章