JNI編程實現(Linux)

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

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

1.使用一些舊的庫
2.需要操作系統交互
3.提高程序的性能

一、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的內容

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

二、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本地編程 快速入門

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