命題作文:對Java Native Interface (JNI) 有一個認識,使用JNI完成一個打印輸出的工作廉涕。
本文通過回答三個環(huán)環(huán)相扣的問題宜雀,來對JNI的功能,作用做一個說明扳躬。
- JNI是干什么的?
- 面對一個問題甚亭,比如打印輸出贷币,JNI的工作流程是什么,每個步驟的目的是什么亏狰?
- 將每個步驟對應的代碼寫出來役纹,跑出來。
一暇唾、JNI是干什么用的
JNI的中文為JAVA本地調(diào)用促脉,本機上用其他語言編寫并且編譯過的代碼(在linux上就是so,在windows上就是dll)策州,可以通過這個本地接口(native interface)在java虛擬機(JVM)上運行[1]瘸味。
JNI一開始是為了本地已編譯語言,尤其是C和C++而設計的够挂,但是它并不妨礙你使用其他語言旁仿,只要調(diào)用約定受支持就可以了。JNI是JDK 的一部分孽糖,用于為Java提供一個本地代碼的接口枯冈。比如:Javah,產(chǎn)生可以調(diào)用Java過程的C過程梭姓,或建立能被Java程序調(diào)用的C過程的頭文件霜幼。
JNI使得運行在JVM虛擬機上的Java代碼能夠操作使用其它語言編寫的應用程序和庫,比如 C/C++以及匯編語言等誉尖。此外JNI提供的某些API還允許你把JVM嵌入到本地應用程序中。
JNI標準至少保證本地代碼能工作在任何Java 虛擬機下實現(xiàn)铸题。
使用JNI來整合本地代碼和Java代碼的步驟是確定的铡恕,沒有再創(chuàng)作的余地,所以讀者可以通過本文的步驟來逐步認識到丢间,其實Java也是"沒有什么不可以 "的探熔。
二、JNI的工作機制是什么
如果現(xiàn)有一個java類需要通過JNI來運行用c編寫的功能烘挫,比如打印輸出hello world诀艰。那么應該進行如下步驟:
- 編寫一個java類柬甥,在這個java類中,使用JNI加載需要調(diào)用的庫函數(shù)和這個庫函數(shù)中的具體方法其垄。
- 編譯這個java類苛蒲。然后運行這個類即可。前提是本地庫函數(shù)要準備好绿满。但是一般而言臂外,這個本地庫函數(shù)是沒有準備好的,或者說是要現(xiàn)場準備的喇颁。所以進入第三步漏健。
- 現(xiàn)在介紹怎么準備java類所要調(diào)用的用c語言編寫編譯過的庫函數(shù)。
- 為這個用c編寫的庫函數(shù)準備一個頭文件橘霎,這樣可以這個讓c編寫的庫函數(shù)與調(diào)用這個庫函數(shù)的java class進行交互蹂楣。頭函數(shù)是早期編程語言葱蝗,包括C語言特有的,用于函數(shù)的聲明,意思就是說有這么一個函數(shù)在衡载,名字叫啥啥啥。所以沃粗,在這里捕仔,這個頭函數(shù)里其實就是放了庫函數(shù)里的方法的聲明。
- 開始用C語言編寫這個庫函數(shù)橡卤。首先是寫成源代碼扮念。在這個源代碼中要調(diào)用為它準備好的頭文件。然后編寫這個庫函數(shù)的主要功能碧库,就是打印輸出hello world柜与,寫好后,編譯成庫函數(shù)嵌灰。一旦這個庫函數(shù)準備好后弄匕,就可以回到第二步,運行java 類文件沽瞭,然后就可以打印輸出了迁匠。
三、以上例子的代碼實現(xiàn)
- 編寫這個java 類
public class HelloJNI
{
static { System.loadLibrary("hello"); // Load native library at runtime
// hello.dll (Windows) or libhello.so (Unixes)
}
// Declare a native method sayHello() that receives nothing and returns void
private native void sayHello();
// Test Driver
public static void main(String[] args) { new HelloJNI().sayHello(); // invoke the native method
}
}
-
將這個java 類編譯成class文件驹溃,命令如下:
javac HelloJNI.java
如果庫函數(shù)已經(jīng)準備好了城丧,就可以運行這個class文件,得到打印輸出的 Hello, World豌鹤!命令如下:
java HelloJNI
否則亡哄,進入第3步: 與第二節(jié)的步驟對應起來
把步驟2得到的class進一步處理,得到庫函數(shù)所需要的頭函數(shù)布疙。命令如下:
javah HelloJNI
用C語言編寫庫函數(shù)蚊惯,代碼如下:
#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"
// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\n");
return;
}
2.1. 將這個編寫好的源程序編譯成o文件愿卸,在linux下的代碼是:
gcc -fpIC -I/usr/lib/jvm/java-8-oracle/include -I/usr/lib/jvm/java-8-oracle/include/linux -c HelloJNI.c
其中,/usr/lib/jvm/java-8-oracle/include表示頭文件jni.h的所在路徑截型,/usr/lib/jvm/java-8-oracle/include/linux表示頭文件jni_md.h所在的路徑趴荸,/usr/lib/jvm/java-8-oracle表示JDK的安裝目錄。
2.2. 將這個o文件進一步連接成庫函數(shù)(so文件)菠劝,在linux下的代碼是:
gcc -o libhello.so -shared HelloJNI.o
2.3. 將so文件所在的路徑加入環(huán)境變量中去赊舶,在linux下的代碼是:
export LD_LIBRARY_PATH=/home/cxy/test
然后就可以回到第二步,用java HelloJNI
命令運行class文件即可赶诊。
結(jié)果如下圖所示:
本文結(jié)束
Second Try
step1. 填寫一個調(diào)用C語言的Java類
public class HelloJNI {
static {
System.loadLibrary("hello"); // Load native library at runtime
// hello.dll (Windows) or libhello.so (Unixes)
}
// Declare a native method sayHello() that receives nothing and returns void
private native void sayHello();
// Test Driver
public static void main(String[] args) {
new HelloJNI().sayHello(); // invoke the native method
}
}
- static initializer invokes System.loadLibrary() to load a library.
- the name of the library is hello/Hello. it is a native library.
- the native library "Hello" contains a native method "sayHello()"
- The native librry "Hello" will be mapped to libhello.so in Linux
- This library should be included in Java's library path, otherwise, the program will throw a UnsatisfiedLinkError. You could include the library into Java Library's path
export LD_LIBRARY_PATH=/home/x/Desktop/JNI
- Next, we declare method "sayHello()" as a native instance method. native means this method is implemented in another language. The sayHello() is contained in the native library loaded.
- The main() method allocate an instance of HelloJNI and invoke the native method sayHello(). new HelloJNI class's instance笼平,and invoke the native method sayHello() in it.
- Question? why declare method sayHello as a native instance method? I mean maybe
System.loadLibrary("hello");
does not know it is a native library? or native library constain method that is not native?
Compile the "HelloJNI.java" into "HelloJNI.class".
javac HelloJNI.java
- javac 是java語言編程編譯器。全稱 javacompilation.javac工具讀由java語言編寫的類和接口的定義舔痪,并將它們編譯成字節(jié)代碼的class文件寓调。My understanding is exe file.
Step2: Create the C/C++ Header file - HelloJNI.h 創(chuàng)建一個C的頭文件,命名為HelloJNI.h
Run javah utility on the class file to create a header file for C/C++ programs: javah HelloJNI
- the utility means utility program that is "HelloJNI.class"
- what does headfile use for? The declaration, tell the program the name of the fun. Then, according to the name of the fun, it can find the body.
- give the C file with headfile "HelloJNI.h", it can be inferred that the c file invokes the function declared in "HelloJNI.h". Thus, to see what does the c file invokes, open it.
-
#include <jni.h>
it include a jni.h headfile, -
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
is a function written by c. -
Java_{package_and_classname}_{function_name}(JNI arguments)
the naming convention for C function 命名規(guī)則锄码。 - Arguments is 命令行參數(shù)夺英。
- JNIEnv*: reference to JNI environment, which lets you access all the JNI fucntions.
- jobject: reference to "this" Java object.
- The extern "C" is recognized by C++ compiler only. It notifies the C++ compiler that these functions are to be compiled using C's function naming protocol (instead of C++ naming protocol). C and C++ have different function naming protocols as C++ support function overloading and uses a name mangling scheme to differentiate the overloaded functions. Read "Name Mangling".
Step 3: C Implementation - HelloJNI.c
#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"
// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\n");
return;
}
- HelloJNI.c 相當于一個子函數(shù),最終目的還是被調(diào)用滋捶,不同的是是被Java寫的程序調(diào)用痛悯,不是被C寫的程序調(diào)用。既然要被調(diào)用重窟,就要把HelloJNI.c里面的函數(shù)的函數(shù)名提取出來载萌,放在頭文件里。這個函數(shù)名就是Java_HelloJNI_sayHello巡扇。所以扭仁,
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
是出現(xiàn)在HelloJNI.h這個頭文件里面的。 - void is the data type厅翔,表示返回值是空值乖坠。
- JNIEXPORT is a key word, JNIEXPORT :在Jni編程中所有本地語言實現(xiàn)Jni接口的方法前面都有一個"JNIEXPORT",這個可以看做是Jni的一個標志,至今為止沒發(fā)現(xiàn)它有什么特殊的用處刀闷。與被C語言調(diào)用做一個區(qū)分標識熊泵。
- JNICALL :這個可以理解為Jni 和Call兩個部分,和起來的意思就是 Jni調(diào)用XXX(后面的XXX就是JAVA的方法名涩赢,就是HelloJNI這個類中的方法sayHello())戈次。
-
Java_HelloJNI_sayHello
就是被JNICALL調(diào)用的部分。也就是Java中的native 方法名筒扒,這里起名字的方式比較特別,是:包名+類名+方法名绊寻。
將這段C寫的子函數(shù)花墩,編譯成動態(tài)庫文件悬秉,這樣就可以被調(diào)用。
step1. gcc -fpic -I/usr/lib/jvm/java-8-oracle/include -I/usr/lib/jvm/java-8-oracle/include/linux -c HelloJNI.c
其中冰蘑,/usr/lib/jvm/java-8-oracle/include表示頭文件jni.h的所在路徑和泌,/usr/lib/jvm/java-8-oracle/include/linux表示頭文件jni_md.h所在的路徑,/usr/lib/jvm/java-8-oracle表示JDK的安裝目錄祠肥。
step2. 將這個o文件進一步連接成庫函數(shù)(so文件)武氓,在linux下的代碼是:gcc -o libhello.so -shared HelloJNI.o
step3. 將so文件所在的路徑加入環(huán)境變量中去,在linux下的代碼是:export LD_LIBRARY_PATH=/home/cxy/test
(注意:路徑不一樣)
step 4:執(zhí)行
然后就可以回到第二步仇箱,用java HelloJNI命令運行class文件即可县恕。
java HelloJNI
執(zhí)行java class文件,從主函數(shù)進去剂桥,new一個HelloJNI類忠烛,并調(diào)用類中的sayHello方法。這個sayHello并沒有像一般的java程序权逗,里面定義了執(zhí)行的代碼語句美尸,沒有輸入也沒有輸出。既然這樣是不是就斷了斟薇?不是师坎,這個sayHello給出了關鍵字native,說明是用其他語言執(zhí)行的堪滨。并且這個sayHello的執(zhí)行部分已經(jīng)寫在了"hello"這個函數(shù)庫里面胯陋。這個函數(shù)庫:hello:就對應了linux中的動態(tài)庫函數(shù)“l(fā)ibhello.so”。那么再調(diào)用這個“l(fā)ibhello.so”就可以繼續(xù)執(zhí)行下去椿猎。執(zhí)行這個動態(tài)庫函數(shù)首先去載入函數(shù)聲明惶岭。發(fā)現(xiàn)函數(shù)聲明JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
這個函數(shù)聲明正好對應了java class “HelloJNI” 中的方法 “sayHello”,也就是說java class中要調(diào)用的“sayHello”在這個動態(tài)庫so中找到了犯眠,那就順著這個聲明去找函數(shù)body按灶,要給出路徑才能找到,找到body就執(zhí)行這個body筐咧,返回空值鸯旁,java class “HelloJNI” 中的方法 “sayHello”獲得空值,執(zhí)行完畢量蕊。