天天看點

java調用c 動态庫設定句柄_linux下 java JNI調用C語言動态連結庫 | 學步園

JNI是Java native interface的簡寫,可以譯作Java原生接口。Java可以通過JNI調用C/C++的庫,這對于那些對性能要求比較高的Java程式無疑是一個福音。

使用JNI也是有代價。大家都知道JAVA程式是運作在JVM之上的,可以做到平台無關。但是如果Java程式通過JNI調用了原生的代碼(比如c/c++等),則Java程式就喪失了平台無關性。最起碼需要重新編譯原生代碼部分。是以應用JNI需要好好權衡,不到萬不得已,請不要選擇JNI,可以選擇替代方案,比如TCP/IP進行程序間通訊等等。這也是為什麼谷歌的Android平台的底層雖然用JNI實作,但是他不建議開發人員用JNI來開發Android上面的應用的原因。将會喪失Android上面的應用程式平台無關性。

下面是在linux下java jni調用C語言動态連結庫的具體操作步驟。

1、建立一個Java程式(Hello.java)定義原生的c/c++函數。

2、用javac編譯Hello.java生成Hello.class。

3、用javah帶-jni參數編譯Hello.class生成Hello.h檔案,該檔案中定義了c的函數原型。在實作c函數的時候需要。

4、建立Hello.c,實作Hello.h定義的函數。

5、編譯Hello.c生成libHello.so。

6、在java虛拟機運作java程式Hello。

第一步,定義一個Java類-- Hello.它提供SayHello方法:

此時應注意兩點:

1.為要使用的每個本地方法編寫本地方法聲明,其聲明方式與普通Java方法接口沒什麼不同,隻是必須指定native關鍵字,如下所示:

public native void SayHello(String strName);

在這個函數中,我們将根據傳進的人名,向某人問好。

2.必須顯式地加載本地代碼庫。當然要調用System.loadLibrary("hello");注意此時不要lib,也不要.so!;我們需在類的一個靜态塊中加載這個庫:

static

{

System.loadLibrary("hello");

}

再加上必要的異常處理就生成如下源檔案Hello.java:

public class Hello

{

static

{

System.loadLibrary("hello");

}

//聲明的本地方法

publicstaitc native void sayHello(String strName);

}

運作指令javac Hello.java生成Hello.class檔案。

第二步,生成本地連結庫。具體過程如下:

1.要為以上定義的類生成Java本地接口頭檔案,需使用javah,Java編譯器的javah功能将根據Hello類生成必要的聲明,此指令将生成Hello.h檔案,我們在共享庫的代碼中要包含它,javah不使預設内部指令,需要指明路徑,它在JDK的bin目錄下,在我的Linux環境下指令如下:

javah Hello

但是出現如下錯誤:

error: cannot access Hello

class file for Hello not found

javadoc: error - Class Hello not found.

Error: No classes were specified on the command line.Try -help.

原因是CLASS_PATH沒有把目前目錄加入其中。是以必須指定classpath為目前目錄。或者在系統CLASS_PATH加入目前路徑。執行如下指令:

javah -classpath . Hello

生成的Hello.h檔案内容的第一句子為#include 但是gcc裡面預設環境可不知道jni.h是什麼東西,jni.h在jdk的$JAVA_HOME/include下面,可進去檢視一下~

2.在與Hello.h相同的路徑下建立一個CPP檔案Hello.cpp。注意,自動生成的那個函數名字很長,并且開頭的Java是大寫的,大小寫很緻命一定要注意。内容如下:

#include "Hello.h"

#include

//與Hello.h中函數聲明相同

JNIEXPORT void JNICALL Java_Hello_sayHello(JNIEnv * env, jclass arg, jstring instring)

{

//從instring字元串取得指向字元串UTF編碼的指針

const jbyte *str =

(const jbyte *)env->GetStringUTFChars( instring, JNI_FALSE );

printf("Hello,%s/n",str);

//通知虛拟機本地代碼不再需要通過str通路Java字元串。

env->ReleaseStringUTFChars( instring, (const char *)str );

return;

}

所有的JNI調用都使用了JNIEnv *類型的指針,習慣上在CPP檔案中将這個變量定義為env,它是任意一個本地方法的第一個參數。env指針指向一個函數指針表,在VC中可以直接用"->"操作符通路其中的函數。

jobject指向在此Java代碼中執行個體化的Java對象LocalFunction的一個句柄,相當于this指針。

後續的參數就是本地調用中有Java程式傳進的參數,本例中隻有一個String型參數。對于字元串型參數,因為在本地代碼中不能直接讀取Java字元串,而必須将其轉換為C /C++字元串或Unicode。以下是三個我們經常會用到的字元串類型處理的函數:

const char* GetStringUTFChars(jstring string,jboolean* isCopy)

3.編譯生成共享庫。

使用GCC時,必須通知編譯器在何處查找此Java本地方法的支援檔案,并且顯式通知編譯器生成位置無關的代碼,在我的環境中按如下過程編譯:

g++ -I /usr/lib/jvm/java-6-sun/include/linux/ -I /usr/lib/jvm/java-6-sun/include/ -fPIC -c Hello.cpp

生成Hello.o

g++ -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 Hello.o

生成libhello.so.1.0

接下來将生成的共享庫拷貝為标準檔案名

cp libhello.so.1.0 libhello.so

或者使用:

g++ -I /usr/lib/jvm/java-6-sun/include/linux/ -I /usr/lib/jvm/java-6-sun/include/ -fPIC -shared -o libLexical.so Lexical.cpp

注意在linux下,動态連結庫的名字必須是lib****.so,必須以lib開頭!4.編寫一個簡單的Java程式來測試我們的本地方法。

将如下源碼存為ToSay.java: