JNI 調(diào)用

一. 簡介

JNI 是Java Native Interface的縮寫,它提供了若干的API 實(shí)現(xiàn)了Java和其他語言的通信(主要是C&C++)。從Java1.1開始,JNI標(biāo)準(zhǔn)成為java平臺(tái)的一部分,它允許Java代碼和其他語言寫的代碼進(jìn)行交互。JNI一開始是為了本地已編譯語言,尤其是C和C++而設(shè)計(jì)的,但是它并不妨礙你使用其他編程語言,只要調(diào)用約定受支持就可以了。使用java與本地已編譯的代碼交互,通常會(huì)喪失平臺(tái)可移植性。但是,有些情況下這樣做是可以接受的,甚至是必須的。例如,使用一些舊的庫,與硬件、操作系統(tǒng)進(jìn)行交互,或者為了提高程序的性能。

二. 編寫

寫一個(gè)調(diào)用本地方法的類主要有以下步驟:

1. 編寫一個(gè)聲明了native 方法的類
public class JniCall {
    static {
        File file = new File("src/main/java/libtest.so");
        System.load(file.getAbsolutePath());        // 加載本地庫
        // System.loadLibrary("libtest");           // 也是加載庫,和上一個(gè)方法有些區(qū)別,稍后再說
    }
    public JniCall() {
    }
    public static native void sayHello();           // 聲明native 方法
}
2. 使用javac 命令編譯上面的類
javac JniCall.java
3. 使用javah 命令生成頭文件
javah com.xxx.JniCall

注意: 在命令中使用的是類的全限定名,所以這個(gè)命令要在com 的上一級目錄執(zhí)行。

執(zhí)行完這個(gè)命令之后會(huì)生成一個(gè)頭文件,頭文件里會(huì)聲明Java 類中聲明的方法,如下

#include <jni.h>
/* Header for class com_sankuai_meituan_dataopt_JniCall */

#ifndef _Included_com_sankuai_meituan_dataopt_JniCall
#define _Included_com_sankuai_meituan_dataopt_JniCall
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_sankuai_meituan_dataopt_JniCall
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_sankuai_meituan_dataopt_JniCall_sayHello            // 生成的方法
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
4. 使用C/C++實(shí)現(xiàn)本地方法

經(jīng)過上一步,我們就可以創(chuàng)建一個(gè)c 文件來實(shí)現(xiàn)這個(gè)方法了

#include "com_sankuai_meituan_dataopt_JniCall.h"

JNIEXPORT void JNICALL Java_com_sankuai_meituan_dataopt_JniCall_sayHello(JNIEnv * env, jobject obj) {       // 自己實(shí)現(xiàn)
    printf("hello world\n");
}
5. 使用gcc 命令編譯c 文件
gcc -c test.c

當(dāng)使用這個(gè)命令時(shí),可能遇到這樣的錯(cuò)誤:

fatal error: 'jni.h' file not found

說是找不到j(luò)ni.h 這個(gè)頭文件,這是因?yàn)閏om_sankuai_meituan_dataopt_JniCall.h 中使用了#include<jni.h>, gcc默認(rèn)找include 的目錄是"/usr/include",在這個(gè)目錄下是找不到j(luò)ni.h文件的,而在Linux 系統(tǒng)中還會(huì)出現(xiàn)找不到j(luò)ni_md.h 這個(gè)文件,這兩個(gè)頭文件的位置分別是:

JAVA_HOME/include             // jni.h
JAVA_HOME/include/linux       // jni_md.h

比如,在Mac 系統(tǒng)中jni.h 文件的路徑就是 JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/include,

而要指定包含的頭文件jni.h 的位置,應(yīng)該加“-I <dir>”參數(shù),如果有多個(gè)路徑就用多個(gè) -I 。所以命令如下:

gcc -I /Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/include/ -c test.c

這時(shí)就會(huì)編譯完成生成test.o 文件

6. 使用gcc 命令將編譯過的 o 文件生成庫文件
gcc -shared test.o -o libtest.so

執(zhí)行完就會(huì)生成一個(gè) so 庫,這時(shí)調(diào)用第一步創(chuàng)建的類的方法就會(huì)調(diào)用native 的實(shí)現(xiàn)。

注意:

  • Linux 下:一定要將本地庫命名成libxxx.so 的形式,"xxx"是你在System.loadLibrary("xxx") 中用到的加載庫名稱。
  • Windows 下:一定要將本地庫命名成xxx.dll的形式(沒有前邊的lib三個(gè)字母),"xxx" 是你在System.loadLibrary("xxx") 中用到的加載庫名稱。

編寫測試代碼:

@Test
public void testJNI() {
    JniCall.sayHello();
}

這時(shí),如果JniCall 中加載本地庫使用的是 System.loadLibrary("libtest"); 就會(huì)拋出異常

java.lang.UnsatisfiedLinkError: no test 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)

那是因?yàn)?java.library.path (Java 加載本地庫的地址)中并不存在當(dāng)前的目錄,所以要想讓Java 找到這個(gè)庫,可以進(jìn)行如下操作:

1. 拷貝libxxx.so 或xxx.dll 到j(luò)ava.library.path 指向的某個(gè)目錄下面(可以通過System.out.println( System.getProperty("java.library.path")) 查看)

2. 命令行執(zhí)行,java -Djava.library.path=/test/libs(path后是庫的路徑)

3. 設(shè)置系統(tǒng)環(huán)境變量

4. 改用System.load(“”) 方法,該方法傳遞一個(gè)so 庫的絕對路徑

三. jni 其他

上面只是簡單介紹了編寫native 方法的一個(gè)流程,關(guān)于jni 調(diào)用,還有許多深層次的東西,比如Java 類型和c、c++ 類型之間的轉(zhuǎn)換,對象的傳遞、多線程等,可以參考以下幾篇文章:

JNI完全手冊
JNI 實(shí)戰(zhàn)全面解析
JNI教程與技術(shù)手冊

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容