在Java中調用C/C++本地庫


JNI是Java Native Interface的英文縮寫, 中文翻譯為本地調用, 自從Java 1.1開始就成為了Java標准的一部分.

C/C++是系統級的編程語言, 可以用來開發任何和系統相關的程序和類庫, 但是Java本身編寫底層的應用比較難實現, 使用JNI可以調用現有的本地庫, 極大地靈活了Java的開發.

C/C++的效率是目前最好的語言, 可以使用C/C++來實現一些實時性非常高的部分. C/C++和Java本身都是非常流行的編程語言, 一些大型軟件中經常使用語言之間的混合編程.

鑒於目前網絡上JNI的文章不是特別多, 我將自己的一些總結寫在這里. 如有錯漏, 歡迎指正!

Java調用C/C++大概有這樣幾個步驟

  1. 編寫帶有native方法的Java類, 使用javac工具編譯Java類
  2. 使用javah來生成與native方法對應的頭文件
  3. 實現相應的頭文件, 並編譯為動態鏈接庫(windows下是.dll, linux下是.so)

下面就完整的介紹一個簡單的Java調用C/C++的例子, 這個例子是來自http://www.ibm.com/developerworks/cn/education/java/j-jni/index.html, 不過其中有一些錯誤, 這個文章是非常不錯的JNI學習資料, 但是非常古老.

編寫Java類

我們來編寫一個Sample1的java類

 1 public class Sample1 {
 2     public native int intMethod(int n);
 3     public native boolean booleanMethod(boolean bool);
 4     public native String stringMethod(String text);
 5     public native int intArrayMethod(int[] intArray);
 6 
 7     public static void main(String[] args) {
 8         System.loadLibrary("Sample1");//沒有后綴名
 9         Sample1 sample = new Sample1();
10         int square = sample.intMethod(5);
11         boolean bool = sample.booleanMethod(true);
12         String text = sample.stringMethod("Java");
13         int sum = sample.intArrayMethod(new int[]{1,2,3,4,5,8,13});        
14 
15         System.out.println("intMethod: " + square);
16         System.out.println("booleanMethod: " + bool);
17         System.out.println("stringMethod: " + text);
18         System.out.println("intArrayMethod: " + sum);
19     }
20 } 
Java Sample1

上面有4個native方法, 分別是4種類型的參數, int, boolean, String, int[].

其中有一句比較重要, 這句話加載了動態類庫

System.loadLibrary("Sample1");

在windows下加載的就是Sample1.dll, 在linux下加載的就是Sample1.so.

本文使用的windowws, 所以后面使用Sample1.dll來表示Sample1動態鏈接庫.

注意: 不可以在代碼中寫上后綴dll或so. 還要保證Sample1.dll在path路徑中. 這個Sample1.dll是我們后面需要編譯出來的東西.

4個native方法就是我們需要用C來實現的方法.

編譯Sample1.java, 使用命令行(windows是cmd, linux下一般是bash)

>javac Sample1.java

可以看到Sample1.class文件

使用javah生成頭文件

在命令行中運行

>javah Sample1

可以在目錄下看到一個新文件Sample1.h

 1 /* DO NOT EDIT THIS FILE - it is machine generated */
 2 #include <jni.h>
 3 /* Header for class Sample1 */
 4 
 5 #ifndef _Included_Sample1
 6 #define _Included_Sample1
 7 #ifdef __cplusplus
 8 extern "C" {
 9 #endif
10 /*
11  * Class:     Sample1
12  * Method:    intMethod
13  * Signature: (I)I
14  */
15 JNIEXPORT jint JNICALL Java_Sample1_intMethod
16   (JNIEnv *, jobject, jint);
17 
18 /*
19  * Class:     Sample1
20  * Method:    booleanMethod
21  * Signature: (Z)Z
22  */
23 JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
24   (JNIEnv *, jobject, jboolean);
25 
26 /*
27  * Class:     Sample1
28  * Method:    stringMethod
29  * Signature: (Ljava/lang/String;)Ljava/lang/String;
30  */
31 JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
32   (JNIEnv *, jobject, jstring);
33 
34 /*
35  * Class:     Sample1
36  * Method:    intArrayMethod
37  * Signature: ([I)I
38  */
39 JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
40   (JNIEnv *, jobject, jintArray);
41 
42 #ifdef __cplusplus
43 }
44 #endif
45 #endif
View Code

我們可以看到其中有四個函數聲明, Java_完整類名_方法名, 完整類名包括了包名, 例如demo.Sample1是完整類名, 對應的這里就是demo_Sample1.

在注釋中我們可以看到這樣一個東西 Signature, 這個是方法的簽名. 關於Signature, 下面通過一個表格來說明.

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數組, [Ljava/lang/String表示String數組
Method (參數簽名)返回類型簽名 例如: ([I)I表示參數類型為int數組, 返回int類型的方法

上面頭文件的第一個函數聲明

JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *, jobject, jint);

注釋中的簽名是 Signature: (I)I

在每個函數的參數列表中都有JNIEnv *和 jobject兩個參數, 這兩個參數稍候說明.

實現頭文件中的函數

可以使用C語言來實現, 也可以使用C++來實現, 下面先說說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(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;
}
View Code

(*env)->GetStringUTFChars()這個方法, 是用來在Java和C之間轉換字符串的, 因為Java本身都使用了雙字節的字符, 而C語言本身都是單字節的字符, 所以需要進行轉換.

JNIEnv *是每個函數都有的參數, 它包含了很多有用的方法, 使用起來類似Java的反射, 也提供了這樣一個編碼轉換的函數.

GetStringUTFChars()和NewStringUTF(), 第一個是從UTF8轉換為C的編碼格式, 第二個是根據C的字符串返回一個UTF8字符串.

ReleaseStringUTFChars()是用來釋放對象的, 在Java中有虛擬機進行垃圾回收, 但是在C語言中, 這些對象必須手動回收. 否則可能造成內存泄漏.

函數的名字一眼看到就可以猜出功能, jni.h中的大部分函數名都是這樣.

如果是C++的話, 這段代碼該怎么寫?

下面是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;
}
C++實現

上述兩端代碼非常相似, 只有一個不同點

C代碼: (*env)->GetStringUTFChars(env, string, 0);

C++代碼: env->GetStringUTFChars(string, 0);

C語言中使用的是結構體的函數指針, 而在C++中使用的還是struct, 我們知道struct在C++中和class的功能是幾乎一樣的, struct也可以用來定義類, 所以env在C++中是個類對象的指針.

編譯和運行

方法一、使用的是微軟編譯器, 編譯C語言版的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

方法二、用vs新建dll項目

1.新建dll空項目:文件-》新建項目-》Win32控制台應用程序-》應用程序類型為DLL-》附加選項為空項目-》完成

2.項目-》屬性-》c/c++ -》常規-》附加包含目錄是%JAVA_HOME%\include和%JAVA_HOME%\include\win32對應的路徑。

3.有的版本的vs可能需要在預處理器中加入_CRT_SECURE_NO_WARNINGS選項。

運行

>java Sample1

注意: 64位版本的JDK可能會在運行時報錯:

java.lang.UnsatisfiedLinkError: ...Sample1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform

如果您有這樣的錯誤, 請使用32位的JDK來重新運行.

運行結果如下:

intMethod: 25
booleanMethod: false
stringMethod: JAVA
intArrayMethod: 36
 
 
 
 
原作者: icejoywoo
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利. 短網址: http://goo.gl/ZiZCi


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM