本篇結(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)步驟如下:
- 編寫java類,類中存在 natice 修飾的方法。
- 使用 javac 編譯 java 類。
- 使用 javah 命令生成頭文件。
- 使用C/C++實(shí)現(xiàn)本地方法,該方法定義可以在 javah 生成的頭文件中找到。
- 生成動(dòng)態(tài)鏈接庫。
- 執(zhí)行 java(java -Djava.library.path=XX XXX 或者拷貝動(dòng)態(tài)庫至 java.library.path 本地庫搜索目錄下)。
再來個(gè)詳細(xì)點(diǎn)的英文版,就不翻譯了:
- create a java project, create a java source file and write some code.
- declare native method, then load the linux native 'so' library.
- javac -d . XXX.java
- javah -d output-dir -jni -classpath xxx-dir package.path.ClassName
- create a c source file, write a method declared in the c file that generated by the command javah
- gcc -D_REENTRANT -fPIC -I
$JAVA_HOME/include -I$JAVA_HOME/include/linux -c xxx.c- 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ò)誤是:
- 無法找到動(dòng)態(tài)鏈接。它所產(chǎn)生的錯(cuò)誤消息是:java.lang.UnsatisfiedLinkError。這通常指無法找到共享庫,或者無法找到共享庫內(nèi)特定的本機(jī)方法。
- 無法找到共享庫文件。當(dāng)用 System.loadLibrary(String libname) 方法(參數(shù)是文件名)裝入庫文件時(shí),請(qǐng)確保文件名拼寫正確以及沒有指定擴(kuò)展名。還有,確保庫文件的位置在類路徑中,從而確保JVM 可以訪問該庫文件。
- 無法找到具有指定說明的方法。確保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ù)類型和類型簽名