用JNI進行Java編程---從C/C++程序調用Java代碼

概述

JNI 允許您從本機代碼內調用 Java 類方法。要做到這一點,通常必須使用 Invocation API 在本機代碼內創建和初始化一個 JVM。下列是您可能決定從 C/C++ 代碼調用 Java 代碼的典型情況:

  • 希望實現的這部分代碼是平臺無關的,它將用於跨多種平臺使用的功能。

  • 需要在本機應用程序中訪問用 Java 語言編寫的代碼或代碼庫。

  • 希望從本機代碼利用標準 Java 類庫。

從 C/C++ 程序調用 Java 代碼的四個步驟

從 C/C++ 調用 Java 方法過程的四個步驟如下:

  1. 編寫 Java 代碼。這個步驟包含編寫一個或多個 Java 類,這些類實現(或調用其它方法實現)您想要訪問的功能。

  2. 編譯 Java 代碼。在能夠使用這些 Java 類之前,必須成功地將它們編譯成字節碼。

  3. 編寫 C/C++ 代碼。這個代碼將創建和實例化 JVM,並調用正確的 Java 方法。

  4. 運行本機 C/C++ 應用程序。將運行應用程序以查看它是否正常工作。我們還將討論一些用於處理常見錯誤的技巧。

步驟 1:編寫 Java 代碼

我們從編寫一個或多個 Java 源代碼文件開始,這些文件將實現我們想要本機 C/C++ 代碼使用的功能。

下面顯示了一個 Java 代碼示例 Sample2.java:

 1. public class Sample2
 2. {
 3.   public static int intMethod(int n) {
 4.       return n*n;
 5.   }
 6.
 7.   public static boolean booleanMethod(boolean bool) {
 8.        return !bool;
 9.   }
10. }

注:Sample2.java 實現了兩個 static Java 方法:intMethod(int n) 和 booleanMethod(boolean bool)(分別在第 3 行和第 7 行)。static 方法是一種不需要與對象實例關聯的類方法。調用 static 方法要更容易些,因爲不必實例化對象來調用它們。

步驟 2:編譯 Java 代碼

接下來,我們將 Java 代碼編譯成字節碼。完成這一步的方法之一是使用隨 SDK 一起提供的 Java 編譯器 javac。 使用的命令是:

javac Sample1.java

步驟 3:編寫 C/C++ 代碼

即使是在本機應用程序中運行,所有 Java 字節碼也必須在 JVM 中執行。因此 C/C++ 應用程序必須包含用來創建和初始化 JVM 的調用。爲了方便我們,SDK 包含了作爲共享庫文件(jvm.dll 或 jvm.so)的 JVM,這個庫文件可以嵌入到本機應用程序中。

讓我們先從瀏覽一下 C 和 C++ 應用程序的整個代碼開始,然後對兩者進行比較。

帶有嵌入式 JVM 的 C 應用程序

Sample2.c 是一個帶有嵌入式 JVM 的簡單的 C 應用程序:

 1. #include <jni.h>
 2.
 3. #ifdef _WIN32
 4. #define PATH_SEPARATOR ';'
 5. #else
 6. #define PATH_SEPARATOR ':'
 7. #endif
 8.
 9. int main()
10. {
11.   JavaVMOption options[1];
12.   JNIEnv *env;
13.   JavaVM *jvm;
14.   JavaVMInitArgs vm_args;
15.   long status;
16.   jclass cls;
17.   jmethodID mid;
18.   jint square;
19.   jboolean not;
20.
21.   options[0].optionString = "-Djava.class.path=.";
22.   memset(&vm_args, 0, sizeof(vm_args));
23.   vm_args.version = JNI_VERSION_1_2;
24.   vm_args.nOptions = 1;
25.   vm_args.options = options;
26.   status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
27.
28.   if (status != JNI_ERR)
29.   {
30.     cls = (*env)->FindClass(env, "Sample2");
31.     if(cls !=0)
32.     { mid = (*env)->GetStaticMethodID(env, cls, "intMethod", "(I)I");
33.       if(mid !=0)
34.       { square = (*env)->CallStaticIntMethod(env, cls, mid, 5);
35.				printf("Result of intMethod: %d\n", square);
36.       }
37.
38.       mid = (*env)->GetStaticMethodID(env, cls, "booleanMethod", "(Z)Z");
39.       if(mid !=0)
40.	  { not = (*env)->CallStaticBooleanMethod(env, cls, mid, 1);
41.         printf("Result of booleanMethod: %d\n", not);
42.       }
43.     }
44.
45.     (*jvm)->DestroyJavaVM(jvm);
46.     return 0;
47/   }
48.   else
49.   return -1;
50. }

帶有嵌入式 JVM 的 C++ 應用程序

Sample2.cpp 是一個帶有嵌入式 JVM 的 C++ 應用程序:

 1. #include <jni.h>
 2.
 3. #ifdef _WIN32
 4. #define PATH_SEPARATOR ';'
 5. #else
 6. #define PATH_SEPARATOR ':'
 7. #endif
 8.
 9. int main()
10. {
11.	  JavaVMOption options[1];
12.	  JNIEnv *env;
13.	  JavaVM *jvm;
14.	  JavaVMInitArgs vm_args;
15.	  long status;
16.	  jclass cls;
17.	  jmethodID mid;
18.	  jint square;
19.	  jboolean not;
20.
21.	  options[0].optionString = "-Djava.class.path=.";
22.	  memset(&vm_args, 0, sizeof(vm_args));
23.	  vm_args.version = JNI_VERSION_1_2;
24.	  vm_args.nOptions = 1;
25.	  vm_args.options = options;
26.	  status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
27.
28.	  if (status != JNI_ERR)
29.   {
30.	    cls = (*env)->FindClass(env, "Sample2");
31.	    if(cls !=0)
32.     {   mid = (*env)->GetStaticMethodID(env, cls, "intMethod", "(I)I");
33.	        if(mid !=0)
34.	        {  square = (*env)->CallStaticIntMethod(env, cls, mid, 5);
35.		       printf("Result of intMethod: %d\n", square);
36.	        }
37.
38.	        mid = (*env)->GetStaticMethodID(env, cls, "booleanMethod", "(Z)Z");
39.	        if(mid !=0)
40.	        {  not = (*env)->CallStaticBooleanMethod(env, cls, mid, 1);
41.		       printf("Result of booleanMethod: %d\n", not);
42.	        }
43.     }
44.
45.	    (*jvm)->DestroyJavaVM(jvm);
46.    return 0;
47.   }
48.	  else
49.	    return -1;
50. }


C 和 C++ 實現的比較

C 和 C++ 代碼幾乎相同;唯一的差異在於用來訪問 JNI 函數的方法。在 C 中,爲了取出函數指針所引用的值,JNI 函數調用前要加一個 (*env)-> 前綴。在 C++ 中,JNIEnv 類擁有處理函數指針查找的內聯成員函數。因此,雖然這兩行代碼訪問同一函數,但每種語言都有各自的語法,如下所示。

C 語法: cls = (*env)->FindClass(env, "Sample2");
C++ 語法: cls = env->FindClass("Sample2");

對 C 應用程序更深入的研究

我們剛纔編寫了許多代碼,但它們都做些什麼呢? 在執行步驟 4 之前,讓我們更深入地研究一下 C 應用程序的代碼。我們將先瀏覽一些必要的步驟,包括準備本機應用程序以處理 Java 代碼、將 JVM 嵌入本機應用程序,然後從該應用程序內找到並調用 Java 方法。

包括 jni.h 文件

我們從 C 應用程序中所包括的 jni.h C 頭文件開始,如下面的代碼樣本中所示:

#include <jni.h>

jni.h 文件包含在 C 代碼中所需要的 JNI 的所有類型和函數定義。

聲明變量

接下來,聲明所有希望在程序中使用的變量。JavaVMOption options[] 具有用於 JVM 的各種選項設置。當聲明變量時,確保所聲明的 JavaVMOption options[] 數組足夠大,以便能容納您希望使用的所有選項。在本例中,我們使用的唯一選項就是類路徑選項。因爲在本示例中,我們所有的文件都在同一目錄中,所以將類路徑設置成當前目錄。可以設置類路徑,使它指向任何您希望使用的目錄結構。

以下代碼聲明瞭用於 Sample2.c 的變量:

JavaVMOption options[1];
JNIEnv *env;
JavaVM *jvm;
JavaVMInitArgs vm_args;

注:

  • JNIEnv *env 表示 JNI 執行環境。

  • JavaVM jvm 是指向 JVM 的指針。我們主要使用這個指針來創建、初始化和銷燬 JVM。

  • JavaVMInitArgs vm_args 表示可以用來初始化 JVM 的各種 JVM 參數。

設置初始化參數

JavaVMInitArgs 結構表示用於 JVM 的初始化參數。在執行 Java 代碼之前,可以使用這些參數來定製運行時環境。正如您所見,這些選項是一個參數而 Java 版本是另一個參數。按如下所示設置了這些參數:

vm_args.version = JNI_VERSION_1_2;
vm_args.nOptions = 1;
vm_args.options = options;

設置類路徑

接下來,爲 JVM 設置類路徑,以使它能找到所需要的 Java 類。在這個特定示例中,因爲 Sample2.class 和 Sample2.exe 都位於同一目錄中,所以將類路徑設置成當前目錄。我們用來爲 Sample2.c 設置類路徑的代碼如下所示:

options[0].optionString = "-Djava.class.path=."; 
// same text as command-line options for the java.exe JVM

爲 vm_args 留出內存

在可以使用 vm_args 之前,必需爲它留出一些內存。一旦設置了內存,就可以設置版本和選項參數了,如下所示:

    memset(&vm_args, 0, sizeof(vm_args));  // set aside enough memory for vm_args
    vm_args.version = JNI_VERSION_1_2;         // version of Java platform
    vm_args.nOptions = 1;                      // same as size of options[1]
    vm_args.options = options;

創建 JVM

處理完所有設置之後,現在就準備創建 JVM 了。先從調用方法開始:

JNI_CreateJavaVM(JavaVM **jvm, void** env, JavaVMInitArgs **vm_args)

如果成功,則這個方法返回零,否則,如果無法創建 JVM,則返回 JNI_ERR

查找並裝入 Java 類

一旦創建了 JVM 之後,就可以準備開始在本機應用程序中運行 Java 代碼。首先,需要使用 FindClass() 函數查找並裝入 Java 類,如下所示:

cls = (*env)->FindClass(env, "Sample2");

cls 變量存儲執行 FindClass() 函數後的結果。 如果找到該類,則 cls 變量表示該 Java 類的句柄。如果不能找到該類,則 cls 將爲零。

查找 Java 方法

接下來,我們希望用 GetStaticMethodID() 函數在該類中查找某個方法。我們希望查找方法 intMethod,它接收一個 int 參數並返回一個 int。以下是查找 intMethod 的代碼:

mid = (*env)->GetStaticMethodID(env, cls, "intMethod", "(I)I");

mid 變量存儲執行 GetStaticMethodID() 函數後的結果。如果找到了該方法,則 mid 變量表示該方法的句柄。如果不能找到該方法,則 mid 將爲零。

請記住,在本示例中,我們正在調用 static Java 方法。 那就是我們使用 GetStaticMethodID() 函數的原因。GetMethodID() 函數與GetStaticMethodID() 函數的功能一樣,但它用來查找實例方法。

如果正在調用構造器,則方法的名稱爲“<init>”。 要了解更多關於調用構造器的知識,請參閱錯誤處理。 要了解更多關於用來指定參數類型的代碼以及關於如何將 JNI 類型映射到 Java 原始類型的知識,請參閱附錄 。

調用 Java 方法

最後,我們調用 Java 方法,如下所示:

square = (*env)->CallStaticIntMethod(env, cls, mid, 5);

CallStaticIntMethod() 方法接受 cls(表示類)、mid(表示方法)以及用於該方法一個或多個參數。在本例中參數是 int 5。

您還會遇到 CallStaticXXXMethod() 和 CallXXXMethod() 之類的方法。這些方法分別調用靜態方法和成員方法,用方法的返回類型(例如,ObjectBooleanByteCharIntLong 等等)代替變量 XXX

步驟 4:運行應用程序

現在準備運行這個 C 應用程序,並確保代碼正常工作。當運行 Sample2.exe 時,應該可以得到如下結果:

PROMPT>Sample2
Result of intMethod: 25
Result of booleanMethod: 0

PROMPT>

故障排除

JNI 的 Invocation API 有點麻煩,因爲它是用 C 語言定義的,而 C 語言基本上不支持面向對象編程。結果是,它很容易遇到問題。下面是一份檢查表,它可能有助於您避免一些較常見的錯誤。

  • 請總是確保正確設置了引用。例如,當使用 JNI_CreateJavaVM() 方法創建 JVM 時,確保它返回零。還請確保,在使用FindClass() 和 GetMethodID() 方法之前,它們的引用設置不是零。

  • 請檢查方法名是否拼寫正確以及是否適當地轉換了方法說明。還請確保,對靜態方法使用了 CallStaticXXXMethod() 以及對成員方法使用了 CallXXXMethod()

  • 確保使用任何 Java 類所需的特殊的參數或選項來初始化 JVM。例如,如果 Java 類需要大量內存,則可能需要增加堆的最大大小選項。

  • 請總是確保正確設置了類路徑。使用嵌入式 JVM 的本機應用程序必須能夠找到 jvm.dll 或 jvm.so 共享庫。

結束語

儘管從 C 調用 Java 方法確實需要相當高級的“類面向對象編程”技術,但這對經驗豐富的 C 程序員而言相對比較簡單。儘管 JNI 支持 C 和 C++,但 C++ 接口要更清晰些,通常比 C 接口更可取。

要記住很重要的一點是:可以用單個 JVM 來裝入和執行多個類和方法。如果每次從本機代碼與 Java 交互都創建和銷燬 JVM,則會浪費資源並降低性能。

發佈了29 篇原創文章 · 獲贊 20 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章