JNI的初級探索

JNI簡介

JNI是Java Native Interface的縮寫,中文爲Java本地接口。使用此種方式,可以對C/C++代碼進行調用,但是,其在本質上是對C/C++生成的動態庫進行調用而不是直接對C/C++代碼進行調用。從Java1.1開始,Java Native Interface(JNI)標準成爲java平臺的一部分,它允許Java代碼和其他語言寫的代碼進行交互。JNI一開始是爲了本地已編譯語言,尤其是C和C++而設計的,但是它並不妨礙你使用其他語言,只要調用約定受支持就可以了。

使用java與本地已編譯的代碼交互,通常會喪失平臺可移植性。但是,有些情況下這樣做是可以接受的,甚至是必須的,比如,使用一些舊的庫,與硬件、操作系統進行交互,或者爲了提高程序的性能。JNI標準至少保證本地代碼能工作在任何Java 虛擬機實現下。

JNI其實是JAVA和其他編程語言代碼的橋樑,通過JNI,兩種代碼之間可以進行無障礙的對接。從下圖的結構中,可以清晰知道JAVA、JNI和其他語言之間的關係。


JNI編程實例

下面我們通過代碼實例,來進一步理解JNI的實現過程。

JNI的實現過程可以簡單概括爲如下幾個步驟:
(1)編寫帶有native聲明的方法的java類
(2)使用javac命令編譯所編寫的java類 (操作命令如:javac TestJni.java)
(3)使用javah命令生成擴展名爲.h的頭文件 (操作命令如:javah TestJni)
   注意:進行生成頭文件時候不要在文件名後面加上後綴
(4)使用C/C++實現JAVA本地方法
(5)將C/C++編寫的文件,生成動態連接庫(.so後綴文件)
(6)OK


第一步:編寫帶有native聲明的方法的java類
(說明:如下代碼在Linux redhat中實現)

public class TestJni
{
	public native void show();
	
	static
	{
		//本處使用的是System.load(),所以使用的是絕對路徑。而System.loadLibrary()使用的是java.library.ptah路徑,且不帶後綴和lib前綴  
		System.load("/home/zdh/c_test/TestJni/libTestJni.so");
		//System.loadLibrary("TestJni");
	}
	
	public static void main(String args[])
	{
		TestJni obj = new TestJni();
		System.out.println("======1======");
		obj.show();
		System.out.println("======2======");
	}
}

注意:本處的java程序並沒有引入package。目的是減少CLASSPATH對理解JNI的干擾。

第二步:使用javac命令編譯所編寫的java類

將以上代碼保存爲TestJni.java,並保存在TestJni這個文件夾中。

進入TestJni文件夾,輸入命令javac TestJni.java,對java文件進行編譯。如果java文件沒有錯誤,會在TestJni目錄中生成TestJni.class字節碼文件。


第三步:使用javah命令生成擴展名爲.h的頭文件

在TestJni目錄中輸入命令javah TestJni。這麼命令執行之後,會在當前目錄中生成和java類同名的C/C++頭文件TestJni.h。
打開TestJni.h文件,看看裏面的內容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestJni */

#ifndef _Included_TestJni
#define _Included_TestJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     TestJni
 * Method:    show
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_TestJni_show
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

頭文件中聲明瞭JNIEXPORT void JNICALL Java_TestJni_show(JNIEnv *, jobject);
這樣一個函數。其實這個就是經過JNI包裝的,需要我們用C/C++代碼實現JNI接口(即JAVA本地方法)。在我們後面加載的動態庫中,我們將用C/C++代碼來實現這個函數。

第四步:使用C/C++實現JAVA本地方法
創建TestJni.cpp文件,在其中輸入如下代碼:
#include "TestJni.h"	//這個頭文件是javah生成的頭文件,在這裏將它包含進來
#include "stdio.h"
#include "string.h"

JNIEXPORT void JNICALL Java_TestJni_show(JNIEnv *, jobject)
{
	printf("=====hzdeng jni======\n");
}
這個C++代碼中僅實現了一個接口,接口中只進行了一個操作,即打印“=====hzdeng jni======”這一行信息。


第五步:將C/C++編寫的文件,生成動態連接庫
這裏需要注意的是,需要知道你是用的Linux版本中,JDK的安裝目錄。
首先需要知道當前Linux的JDK版本號
在控制檯運行java -version即可輸出當前系統中的JDK版本號。

JDK的安裝目錄一般都在/usr/lib/jvm中,不同系統可能會有些差異。需要知道JDK安裝目錄的原因是:我們要知道jni.h和jni_md.h這兩個頭文件的路徑。待會兒編譯cpp文件的時候,需要將他們鏈接進來。

在我的系統中,jni.h和jni_md.h的路徑分別爲:
/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/include
/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/include/linux/

知道了這兩個關鍵頭文件的路徑之後,下面我們就可以將cpp文件編譯成so後綴的動態庫了。

運行如下命令:
g++ -I/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/include/linux/ -I/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/include -fPIC -shared -o libTestJni.so Testjni.c

執行完畢後,將會在當前目錄生成libTestJni.so動態庫文件。現在好了,我們需要的C/C++動態庫已經生成了。

注意:在編譯的時候如果提示找不到,jni.h頭文件,那麼就打開TestJNI.h頭文件將include <jni.h> 改爲#include "jni.h"既可以解決

第六步:運行查看結果
在當前目錄輸入如下命令:java TestJni 即可看到如下的效果
======1======
=====hzdeng jni======
======2======


=====hzdeng jni======這一行信息,我們是在C++代碼中打印出來的。
如今,我們運行java代碼,也能夠輸出這個信息。足矣說明,我們已經成功在JAVA代碼中調用了C++的代碼了。


常見問題:

在這些JNI 的步驟中,最容易出現問題的,其實是庫路徑的設置問題。
此處我們用的是絕對路徑,不會有什麼問題。如果我們將System.load()改成System.loadLibrary()。
然後運行java TestJni,會看到如下打印:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no libTestJni in java.library.path
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1698)
        at java.lang.Runtime.loadLibrary0(Runtime.java:840)
        at java.lang.System.loadLibrary(System.java:1047)
        at TestJni.<clinit>(TestJni.java:9)
此打印是說明,程序在java.library.path指定的路徑下,沒有找到相應的動態庫文件。

這樣的話,就需要做相應的修改了。

首先,需要設置Linux下的java.library.path環境變量。
方法如下:

(1)打開用戶工作目錄下的配置文件 vim .bash_profile
在這個文件中添加如下內容:
LD_LIBRARY_PATH=/home/zdh/lib_java
export LD_LIBRARY_PATH

(2)退出當前控制檯,重新登錄新的控制檯。

(3)將libTestJni.so動態庫拷貝到,上一步環境變量設置的目錄/home/zdh/lib_java中。

(3)將java代碼中,加載動態庫的語句改爲:System.loadLibrary("TestJni");
注意:此處庫不帶後綴,也不帶lib前綴

(4)重新編譯JAVA文件,然後運行
javac TestJni.java
java TestJni

進行上面的操作後,程序可以正常打印了
======1======
=====hzdeng jni======
======2======


到目前爲止,JNI的用法已經基本可以明瞭了。但值得注意的是,此java文件並沒有涉及到包,所以,遇到的問題是比較少的。如果在java程序中引入包的概念,又會有什麼問題呢?

請看下回分解吧~~嘻嘻


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