java調(diào)用本地方法--jni簡介

本篇結(jié)構(gòu):

  • 簡介
  • JNI實(shí)現(xiàn)步驟
  • JNI實(shí)例--簡單調(diào)用
  • 故障排除
  • CUDA 生成動(dòng)態(tài)鏈接庫 指令
  • 參考博文

一、簡介

JAVA以其跨平臺(tái)的特性深受人們喜愛,而又正由于它的跨平臺(tái)的目的,使得它和本地機(jī)器的各種內(nèi)部聯(lián)系變得很少,約束了它的功能。

有時(shí)可能存在需要訪問的系統(tǒng)特性和設(shè)備通過java平臺(tái)無法實(shí)現(xiàn),又或者某個(gè)部分需要非常高的性能,這時(shí)可能就要犧牲跨平臺(tái)的優(yōu)勢(shì),一種解決方法就是尋求系統(tǒng)級(jí)的編程語言C/C++的幫助,這就需要用到JNI。

JNI是Java Native Interface的英文縮寫,中文翻譯為本地調(diào)用。JAVA通過JNI調(diào)用本地方法,而本地方法是以庫文件的形式存放的(在WINDOWS平臺(tái)上是DLL文件形式,在UNIX機(jī)器上是SO文件形式)。通過調(diào)用本地的庫文件的內(nèi)部方法,使JAVA可以實(shí)現(xiàn)和本地機(jī)器的緊密聯(lián)系,調(diào)用系統(tǒng)級(jí)的各接口方法。

開發(fā)JNI程序會(huì)受到系統(tǒng)環(huán)境的限制,因?yàn)橛肅/C++語言寫出來的代碼或模塊,編譯過程當(dāng)中要依賴當(dāng)前操作系統(tǒng)環(huán)境所提供的一些庫函數(shù),并和本地庫鏈接在一起。而且編譯后生成的二進(jìn)制代碼只能在本地操作系統(tǒng)環(huán)境下運(yùn)行,因?yàn)椴煌牟僮飨到y(tǒng)環(huán)境,有自己的本地庫和CPU指令集,而且各個(gè)平臺(tái)對(duì)標(biāo)準(zhǔn)C/C++的規(guī)范和標(biāo)準(zhǔn)庫函數(shù)實(shí)現(xiàn)方式也有所區(qū)別。這就造成使用了JNI接口的JAVA程序,不再像以前那樣自由的跨平臺(tái)。如果要實(shí)現(xiàn)跨平臺(tái),就必須將本地代碼在不同的操作系統(tǒng)平臺(tái)下編譯出相應(yīng)的動(dòng)態(tài)庫。

二、JNI實(shí)現(xiàn)步驟

JNI的實(shí)現(xiàn)步驟如下:

  1. 編寫java類,類中存在 natice 修飾的方法。
  2. 使用 javac 編譯 java 類。
  3. 使用 javah 命令生成頭文件。
  4. 使用C/C++實(shí)現(xiàn)本地方法,該方法定義可以在 javah 生成的頭文件中找到。
  5. 生成動(dòng)態(tài)鏈接庫。
  6. 執(zhí)行 java(java -Djava.library.path=XX XXX 或者拷貝動(dòng)態(tài)庫至 java.library.path 本地庫搜索目錄下)。

再來個(gè)詳細(xì)點(diǎn)的英文版,就不翻譯了:

  1. create a java project, create a java source file and write some code.
  2. declare native method, then load the linux native 'so' library.
  3. javac -d . XXX.java
  4. javah -d output-dir -jni -classpath xxx-dir package.path.ClassName
  5. create a c source file, write a method declared in the c file that generated by the command javah
  6. gcc -D_REENTRANT -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -c xxx.c
  7. gcc -shared xxx.o -o libxxx.so

三、JNI實(shí)例--簡單調(diào)用

3.1、編寫Java類

需要JNI實(shí)現(xiàn)的方法應(yīng)當(dāng)用native關(guān)鍵字聲明,在類中,用System.loadLibrary()方法加載需要的動(dòng)態(tài)鏈接庫:

package jni.demo;

public class DemoJni {

    static {
        try {
            //調(diào)用動(dòng)態(tài)鏈接庫
            System.loadLibrary("sayHello");
        } catch (Exception e) {
            System.err.println("load native library [sayHello] failed.");
        }
        System.out.println("load native library [sayHello] succeed.");
    }

    // native關(guān)鍵字聲明本地方法
    public native static String sayHello(String name);

    public static void main(String[] args) {
        System.out.println("Hello " + sayHello("Dean."));
    }
}

3.2、編譯java類

javac -d . -encoding UTF-8 DemoJni.java

PS: -d 文件夾 解釋:

設(shè)置類文件的目標(biāo)文件夾。

假設(shè)某個(gè)類是一個(gè)包的組成部分,則 javac 將把該類文件放入反映包名的子文件夾中,必要時(shí)創(chuàng)建文件夾。比如,假設(shè)指定 -d c:\cp,而且該類名叫 com.package.MyClass,那么類文件就叫作c:\cp\com\package\MyClass.class。

若未指定 -d 選項(xiàng),則 javac 將把類文件放到與源文件同樣的文件夾中。

注意: -d 選項(xiàng)指定的文件夾不會(huì)被自己主動(dòng)增加到用戶類路徑中。

3.3、生成相關(guān)JNI方法的頭文件

javah -d jnilib -jni jni.demo.DemoJni

這個(gè)過程的實(shí)現(xiàn)一般是通過利用javah -jni * class生成的(-jni可以省略),也可以手工生成該文件;但是由于 Java 虛擬機(jī)是根據(jù)一定的命名規(guī)范完成對(duì)JNI方法的調(diào)用,所以手工編寫頭文件需要特別小心。

生成的頭文件代碼如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jni_demo_DemoJni */

#ifndef _Included_jni_demo_DemoJni
#define _Included_jni_demo_DemoJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     jni_demo_DemoJni
 * Method:    sayHello
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_jni_demo_DemoJni_sayHello
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

JNI函數(shù)名稱(eg:Java_jni_demo_DemoJni_sayHello)分為三部分:首先是Java關(guān)鍵字,供Java虛擬機(jī)識(shí)別;然后是調(diào)用者類名稱(全限定的類名,其中用下劃線代替名稱分隔符);最后是對(duì)應(yīng)的方法名稱,各段名稱之間用下劃線分割。

JNI函數(shù)的參數(shù)也由三部分組成:首先是JNIEnv *,是一個(gè)指向JNI運(yùn)行環(huán)境的指針;第二個(gè)參數(shù)隨本地方法是靜態(tài)還是非靜態(tài)而有所不同一一非靜態(tài)本地方法的第二個(gè)參數(shù)是對(duì)對(duì)象的引用,而靜態(tài)本地方法的第二個(gè)參數(shù)是對(duì)其Java類的引用;其余的參數(shù)對(duì)應(yīng)通常Java方法的參數(shù),參數(shù)類型需要根據(jù)一定規(guī)則進(jìn)行映射。

3.4、使用C/C++實(shí)現(xiàn)本地方法

#include "stdio.h"
#include "jni_demo_DemoJni.h"

JNIEXPORT jstring JNICALL Java_jni_demo_DemoJni_sayHello
  (JNIEnv *env, jclass jc, jstring name)
{
    return name;
}

在編碼過程中,需要注意變量的長度問題,例如Java的整型變量長度為32位,而C語言為16位,所以要仔細(xì)核對(duì)變量類型映射表,防止在傳值過程中出現(xiàn)問題。

本例中是String。

3.5、生成動(dòng)態(tài)鏈接庫

gcc -D_REENTRANT -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -c sayHello.c

gcc -shared sayHello.o -o libsayHello.so

-fPIC 作用于編譯階段,在編譯動(dòng)態(tài)庫時(shí)(.so文件)告訴編譯器產(chǎn)生與位置無關(guān)代碼(Position-Independent Code),若未指定-fPIC選項(xiàng)編譯.so文件,則在加載動(dòng)態(tài)庫時(shí)需進(jìn)行重定向。

-c 對(duì)源代碼進(jìn)行預(yù)處理、編譯、匯編,但不執(zhí)行鏈接,產(chǎn)生的是源代碼的目標(biāo)文件(*.o)。

-l 用來指定程序要鏈接的庫,-l參數(shù)緊接著就是庫名。

-shared 生成共享目標(biāo)文件。

3.6、運(yùn)行java

java -Djava.library.path=jnilib jni.demo.DemoJni

不出意外,會(huì)輸出:

load native library [sayHello] succeed.
Hello Dean.

四、故障排除

當(dāng)使用 JNI 從Java 程序訪問本機(jī)代碼時(shí),遇到的三個(gè)最常見的錯(cuò)誤是:

  1. 無法找到動(dòng)態(tài)鏈接。它所產(chǎn)生的錯(cuò)誤消息是:java.lang.UnsatisfiedLinkError。這通常指無法找到共享庫,或者無法找到共享庫內(nèi)特定的本機(jī)方法。
  2. 無法找到共享庫文件。當(dāng)用 System.loadLibrary(String libname) 方法(參數(shù)是文件名)裝入庫文件時(shí),請(qǐng)確保文件名拼寫正確以及沒有指定擴(kuò)展名。還有,確保庫文件的位置在類路徑中,從而確保JVM 可以訪問該庫文件。
  3. 無法找到具有指定說明的方法。確保C/C++ 函數(shù)實(shí)現(xiàn)擁有與頭文件中的函數(shù)說明相同的說明。

五、CUDA 生成動(dòng)態(tài)鏈接庫 指令

nvcc -arch sm_20 --compiler-options '-fPIC' -o libkernel.so --shared kernel.cu -I./GPU_TLS -I $JAVA_HOME/include -I $JAVA_HOME/include/linux

六、參考博文

JNI/NDK開發(fā)指南(開山篇)
JNI中的內(nèi)存管理
操作JNI函數(shù)以及復(fù)雜對(duì)象傳遞
JNI的數(shù)據(jù)類型和類型簽名

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

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

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