使用JNI進(jìn)行混合編程:在Java中調(diào)用C/C++本地庫(kù)
JNI是Java Native Interface的英文縮寫(xiě), 中文翻譯為本地調(diào)用, 自從Java 1.1開(kāi)始就成為了Java標(biāo)準(zhǔn)的一部分。
C/C++是系統(tǒng)級(jí)的編程語(yǔ)言, 可以用來(lái)開(kāi)發(fā)任何和系統(tǒng)相關(guān)的程序和類庫(kù), 但是Java本身編寫(xiě)底層的應(yīng)用比較難實(shí)現(xiàn), 使用JNI可以調(diào)用現(xiàn)有的本地庫(kù), 極大地靈活了Java的開(kāi)發(fā)。
C/C++的效率是目前***的語(yǔ)言, 可以使用C/C++來(lái)實(shí)現(xiàn)一些實(shí)時(shí)性非常高的部分. C/C++和Java本身都是非常流行的編程語(yǔ)言, 一些大型軟件中經(jīng)常使用語(yǔ)言之間的混合編程。
鑒于目前網(wǎng)絡(luò)上JNI的文章不是特別多, 我將自己的一些總結(jié)寫(xiě)在這里. 如有錯(cuò)漏, 歡迎指正!
Java調(diào)用C/C++大概有這樣幾個(gè)步驟
- 編寫(xiě)帶有native方法的Java類, 使用javac工具編譯Java類
- 使用javah來(lái)生成與native方法對(duì)應(yīng)的頭文件
- 實(shí)現(xiàn)相應(yīng)的頭文件, 并編譯為動(dòng)態(tài)鏈接庫(kù)(windows下是.dll, linux下是.so)
下面就完整的介紹一個(gè)簡(jiǎn)單的Java調(diào)用C/C++的例子, 這個(gè)例子是來(lái)自http://www.ibm.com/developerworks/cn/education/java/j-jni/index.html, 不過(guò)其中有一些錯(cuò)誤, 這個(gè)文章是非常不錯(cuò)的JNI學(xué)習(xí)資料, 但是非常古老.
編寫(xiě)Java類
我們來(lái)編寫(xiě)一個(gè)Sample1的java類
- public class Sample1 {
- public native int intMethod(int n);
- public native boolean booleanMethod(boolean bool);
- public native String stringMethod(String text);
- public native int intArrayMethod(int[] intArray);
- public static void main(String[] args) {
- System.loadLibrary("Sample1");
- Sample1 sample = new Sample1();
- int square = sample.intMethod(5);
- boolean bool = sample.booleanMethod(true);
- String text = sample.stringMethod("Java");
- int sum = sample.intArrayMethod(new int[]{1,2,3,4,5,8,13});
- System.out.println("intMethod: " + square);
- System.out.println("booleanMethod: " + bool);
- System.out.println("stringMethod: " + text);
- System.out.println("intArrayMethod: " + sum);
- }
- }
上面有4個(gè)native方法, 分別是4種類型的參數(shù), int, boolean, String, int[].
其中有一句比較重要, 這句話加載了動(dòng)態(tài)類庫(kù)
- System.loadLibrary("Sample1");
在windows下加載的就是Sample1.dll, 在linux下加載的就是Sample1.so。
本文使用的windowws, 所以后面使用Sample1.dll來(lái)表示Sample1動(dòng)態(tài)鏈接庫(kù)。
注意:不可以在代碼中寫(xiě)上后綴dll或so. 還要保證Sample1.dll在path路徑中. 這個(gè)Sample1.dll是我們后面需要編譯出來(lái)的東西。
4個(gè)native方法就是我們需要用C來(lái)實(shí)現(xiàn)的方法。
編譯Sample1.java, 使用命令行(windows是cmd, linux下一般是bash)
>javac Sample1.java
可以看到Sample1.class文件
使用javah生成頭文件
在命令行中運(yùn)行
>javah Sample1
可以在目錄下看到一個(gè)新文件Sample1.h
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class Sample1 */
- #ifndef _Included_Sample1
- #define _Included_Sample1
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: Sample1
- * Method: intMethod
- * Signature: (I)I
- */
- JNIEXPORT jint JNICALL Java_Sample1_intMethod
- (JNIEnv *, jobject, jint);
- /*
- * Class: Sample1
- * Method: booleanMethod
- * Signature: (Z)Z
- */
- JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
- (JNIEnv *, jobject, jboolean);
- /*
- * Class: Sample1
- * Method: stringMethod
- * Signature: (Ljava/lang/String;)Ljava/lang/String;
- */
- JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
- (JNIEnv *, jobject, jstring);
- /*
- * Class: Sample1
- * Method: intArrayMethod
- * Signature: ([I)I
- */
- JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
- (JNIEnv *, jobject, jintArray);
- #ifdef __cplusplus
- }
- #endif
- #endif
我們可以看到其中有四個(gè)函數(shù)聲明, Java_完整類名_方法名, 完整類名包括了包名, 例如demo.Sample1是完整類名, 對(duì)應(yīng)的這里就是demo_Sample1.
在注釋中我們可以看到這樣一個(gè)東西 Signature, 這個(gè)是方法的簽名. 關(guān)于Signature, 下面通過(guò)一個(gè)表格來(lái)說(shuō)明。
java類型 | Signature | 備注 |
boolean | Z | |
byte | B | |
char | C | |
short | S | |
int | I | |
long | L | |
float | F | |
double | D | |
void | V | |
object | L用/分割的完整類名 | 例如: Ljava/lang/String表示String類型 |
Array | [簽名 | 例如: [I表示int數(shù)組, [Ljava/lang/String表示String數(shù)組 |
Method | (參數(shù)簽名)返回類型簽名 | 例如: ([I)I表示參數(shù)類型為int數(shù)組, 返回int類型的方法 |
上面頭文件的***個(gè)函數(shù)聲明
- JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *, jobject, jint);
注釋中的簽名是 Signature: (I)I
在每個(gè)函數(shù)的參數(shù)列表中都有JNIEnv *和 jobject兩個(gè)參數(shù), 這兩個(gè)參數(shù)稍候說(shuō)明。
實(shí)現(xiàn)頭文件中的函數(shù)
可以使用C語(yǔ)言來(lái)實(shí)現(xiàn), 也可以使用C++來(lái)實(shí)現(xiàn), 下面先說(shuō)說(shuō)C語(yǔ)言的實(shí)現(xiàn)。
- #include "Sample1.h"
- #include <string.h>
- JNIEXPORT jint JNICALL Java_Sample1_intMethod
- (JNIEnv *env, jobject obj, jint num)
- {
- return num * num;
- }
- JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
- (JNIEnv *env, jobject obj, jboolean boolean)
- {
- return !boolean;
- }
- JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
- (JNIEnv *env, jobject obj, jstring string)
- {
- const char* str = (*env)->GetStringUTFChars(env, string, 0);
- char cap[128];
- strcpy(cap, str);
- (*env)->ReleaseStringUTFChars(env, string, 0);
- return (*env)->NewStringUTF(env, strupr(cap));
- }
- JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
- (JNIEnv *env, jobject obj, jintArray array)
- {
- int i, sum = 0;
- jsize len = (*env)->GetArrayLength(env, array);
- jint *body = (*env)->GetIntArrayElements(env, array, 0);
- for (i = 0; i < len; ++i)
- {
- sum += body[i];
- }
- (*env)->ReleaseIntArrayElements(env, array, body, 0);
- return sum;
- }
(*env)->GetStringUTFChars()這個(gè)方法, 是用來(lái)在Java和C之間轉(zhuǎn)換字符串的, 因?yàn)镴ava本身都使用了雙字節(jié)的字符, 而C語(yǔ)言本身都是單字節(jié)的字符, 所以需要進(jìn)行轉(zhuǎn)換.
JNIEnv *是每個(gè)函數(shù)都有的參數(shù), 它包含了很多有用的方法, 使用起來(lái)類似Java的反射, 也提供了這樣一個(gè)編碼轉(zhuǎn)換的函數(shù).
GetStringUTFChars()和NewStringUTF(), ***個(gè)是從UTF8轉(zhuǎn)換為C的編碼格式, 第二個(gè)是根據(jù)C的字符串返回一個(gè)UTF8字符串.
ReleaseStringUTFChars()是用來(lái)釋放對(duì)象的, 在Java中有虛擬機(jī)進(jìn)行垃圾回收, 但是在C語(yǔ)言中, 這些對(duì)象必須手動(dòng)回收. 否則可能造成內(nèi)存泄漏.
函數(shù)的名字一眼看到就可以猜出功能, jni.h中的大部分函數(shù)名都是這樣.
如果是C++的話, 這段代碼該怎么寫(xiě)?
下面是C++的代碼
- #include "Sample1.h"
- #include <string.h>
- JNIEXPORT jint JNICALL Java_Sample1_intMethod
- (JNIEnv *env, jobject obj, jint num)
- {
- return num * num;
- }
- JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
- (JNIEnv *env, jobject obj, jboolean boolean)
- {
- return !boolean;
- }
- JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
- (JNIEnv *env, jobject obj, jstring string)
- {
- const char* str = env->GetStringUTFChars(string, 0);
- char cap[128];
- strcpy(cap, str);
- env->ReleaseStringUTFChars(string, 0);
- return env->NewStringUTF(strupr(cap));
- }
- JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
- (JNIEnv *env, jobject obj, jintArray array)
- {
- int i, sum = 0;
- jsize len = env->GetArrayLength(array);
- jint *body = env->GetIntArrayElements(array, 0);
- for (i = 0; i < len; ++i)
- {
- sum += body[i];
- }
- env->ReleaseIntArrayElements(array, body, 0);
- return sum;
- }
上述兩端代碼非常相似, 只有一個(gè)不同點(diǎn)
C代碼: (*env)->GetStringUTFChars(env, string, 0);
C++代碼: env->GetStringUTFChars(string, 0);
C語(yǔ)言中使用的是結(jié)構(gòu)體的函數(shù)指針, 而在C++中使用的還是struct, 我們知道struct在C++中和class的功能是幾乎一樣的, struct也可以用來(lái)定義類, 所以env在C++中是個(gè)類對(duì)象的指針.
編譯和運(yùn)行
這里使用的是微軟編譯器, 編譯C語(yǔ)言版的dll
>cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -LD Sample1.c -FeSample1.dll
編譯C++版本的dll
>cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -LD Sample1.cpp -FeSample1.dll
運(yùn)行
>java Sample1
注意: 64位版本的JDK可能會(huì)在運(yùn)行時(shí)報(bào)錯(cuò):
java.lang.UnsatisfiedLinkError: ...Sample1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
如果您有這樣的錯(cuò)誤, 請(qǐng)使用32位的JDK來(lái)重新運(yùn)行.
運(yùn)行結(jié)果如下:
intMethod: 25 booleanMethod: false stringMethod: JAVA intArrayMethod: 36
源代碼下載: Sample1.zip
運(yùn)行其中的build&run.bat文件即可, 如有錯(cuò)誤請(qǐng)根據(jù)實(shí)際情況修改其中的一些參數(shù).
DLL工程文件VC6.0和VS2010的: VC6.0&VS2010.zip
參考文獻(xiàn):
- Scott Stricker, 用 JNI 進(jìn)行 Java 編程, http://www.ibm.com/developerworks/cn/education/java/j-jni/section2.html
- JDK 6u30 docs, Java Native Interface Specification, Chapter 3 JNI Types and Data Structures, Type Signatures.
原文鏈接:http://www.cnblogs.com/icejoywoo/archive/2012/02/22/2363709.html
【編輯推薦】
- Java通過(guò)SSH2協(xié)議運(yùn)行遠(yuǎn)程程序
- Java Socket編程:初識(shí)TCP Socket
- Java Socket編程:如何識(shí)別網(wǎng)絡(luò)主機(jī)
- Java Socket編程:基本的術(shù)語(yǔ)和概念
- Java集合類ArrayList循環(huán)中刪除特定元素