JNI編程實現(Linux)

JNIJava Native Interface的縮寫,是Java平臺的本地調用,從Java1.1就成爲了Java標準的一部分,它容許Java代碼和其它語言的代碼進行互相調用,只要調用約定支持便可,尤爲和C/C++的互相調用。html

雖然使用Java與本地編譯的代碼進行交互,會喪失平臺的可移植性,可是在特定狀況下,這些問題是能夠接受的,如:java

1.使用一些舊的庫
2.須要操做系統交互
3.提升程序的性能c++

1、jni介紹

Java是經過定義native方法,而後用其它語言實現該方法,最後在Java運行時,動態地加載該方法實現,經過調用native的方法,進而實現Java的本地調用。編程

1.實現架構

JVM封裝了各類操做系統的差別性,提供了jni技術,使得開發中能夠經過Java程序調用到操做系統的函數,進而與其它技術進行交互。下圖是Linux平臺jni的調用流程。Java應用程序經過jni接口調用動態連接庫*.so,來實現jni的功能。數組

圖1

2.類型映射

Java基本數據類型與C語言基本數據類型的對應架構

圖2

3.經常使用方法簡介

1) GetStringUTFLength
以字節爲單位返回字符串的UTF-8長度函數

// jsize (JNICALL *GetStringUTFLength)(JNIEnv *env, jstring str)
    int len = (*env)->GetStringUTFLength(env, str);

2) GetStringUTFChars
返回指向字符串的UTF-8字符數組的指針。該數組在被ReleaseStringUTFChars()釋放前將一直有效性能

// const char* (JNICALL *GetStringUTFChars)(JNIEnv *env, jstring str, jboolean *isCopy)
    const char *buf = (*env)->GetStringUTFChars(env, str, NULL);

isCopyJNI_FALSE,不要修改返回值,否則將改變java.lang.String的不可變語義。 通常會把isCopy設爲NULL,不關心Java VM對返回的指針是否直接指向java.lang.String的內容spa

3) ReleaseStringUTFChars
通知虛擬機平臺相關代碼無需再訪問utfutf參數是一個指針,可利用GetStringUTFChars()得到操作系統

// void (JNICALL *ReleaseStringUTFChars)(JNIEnv *env, jstring str, const char* chars)
    (*env)->ReleaseStringUTFChars(env, str, buf);

4) NewStringUTF
利用UTF-8字符數組構造新java.lang.String對象

// jstring (JNICALL *NewStringUTF)(JNIEnv *env, const char *utf)
    (*env)->NewStringUTF(env, "hello");

更多實用方法,請參考jni.h

2、jni實現步驟

下面介紹jni的具體實現步驟,主要是經過Java程序調用C方法,跑通整兒jni的調用流程。

1.編寫java類

編寫JavaHello類,定義一個native的本地方法

public class Hello {
    public native static String sayHello(String name);

    static {
        System.load("你的*.so的絕對路徑");
    }

    public static void main(String[] args) {
        Hello hello = new Hello();
        String ret = hello.sayHello("kelvin");
        System.out.println(ret);
    }
}

2.編譯java類

使用javac命令進行編譯

# javac Hello.java

3.生成本地文件*.h

這是關鍵的一步,主要是生成本地方法簽名,依賴的是上一步的class文件,

# javah -jni Hello

若是你的java源文件有包名,在生成*.sh的時候,也要帶包名轉化的路徑,即用classpath指定包所在的路徑,否則在最後調用時,會報錯:UnsatisfiledLinkError

// java源文件包名
package kelvin.Java.dynamicso;

// 編譯時指定classpath
# javah -classpath /Users/kelvin/Documents -jni kelvin.Java.dynamicso.Hello

4.編寫本地方法

在生成的Hello.h頭文件中,有須要實現的本地方法名,在實現時,要記得指定參數名稱

#include <stdio.h>
#include "Hello.h"

JNIEXPORT jstring JNICALL Java_Hello_sayHello(JNIEnv *env, jclass jc, jstring name)
{
    const char *buf;

    buf = (*env)->GetStringUTFChars(env, name, NULL);
    if (NULL == buf)
    {
        return NULL;
    }

    printf("%s\n", buf);
    
    (*env)->ReleaseStringUTFChars(env, name, buf);

    return (*env)->NewStringUTF(env, "hello");
}

5.製做動態庫

因爲是Linux平臺,須要製做後綴是.so的動態庫,其中,須要指定jni.h的路徑,必要時還須要jni_md.h的路徑,該文件在jdk的目錄中

# gcc -c -fPIC -I/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/include  Hello.c -o Hello.o
# gcc -shared Hello.o -o libhello.so

6.調用動態庫

加載動態庫有2種方式:

1)load():須要指定庫的絕對路徑
2)loadLibrary():須要指定庫的相對路徑,即java.lib.path

如今jni調用的一切都準備好了,進行最後的調用,有正常的打印輸出,代表jni正常調用了

# java Hello
kelvin
hello

以上就是Linux平臺的jni調用方式,下一篇介紹Windows平臺的jni調用方式。。。

參考資料

JNI之String類型
Jni編程(三)c/c++ 獲取java字符串,以及java 獲取c/c++建立的對象
一天掌握Android JNI本地編程 快速入門

相關文章
相關標籤/搜索