從 C/C++ 程序調用 Java 代碼java
概述程序員
JNI 容許您從本機代碼內調用 Java 類方法。要作到這一點,一般必須使用 Invocation API 在本機代碼內建立和初始化一個 JVM。下列是您可能決定從 C/C++ 代碼調用 Java 代碼的典型狀況:shell
從 C/C++ 調用 Java 方法過程的四個步驟以下:編程
咱們從編寫一個或多個 Java 源代碼文件開始,這些文件將實現咱們想要本機 C/C++ 代碼使用的功能。c#
下面顯示了一個 Java 代碼示例 Sample2.java:數組
public class Sample2 { public static int intMethod(int n) { return n*n; } public static boolean booleanMethod(boolean bool) { return !bool; } }
注:Sample2.java 實現了兩個 static
Java 方法:intMethod(int n)
和 booleanMethod(boolean bool)
(分別在第 3 行和第 7 行)。static
方法是一種不須要與對象實例關聯的類方法。調用 static
方法要更容易些,由於沒必要實例化對象來調用它們。jvm
接下來,咱們將 Java 代碼編譯成字節碼。完成這一步的方法之一是使用隨 SDK 一塊兒提供的 Java 編譯器 javac
。 使用的命令是:ide
javac Sample1.java
即便是在本機應用程序中運行,全部 Java 字節碼也必須在 JVM 中執行。所以 C/C++ 應用程序必須包含用來建立和初始化 JVM 的調用。爲了方便咱們,SDK 包含了做爲共享庫文件(jvm.dll 或 jvm.so)的 JVM,這個庫文件能夠嵌入到本機應用程序中。函數
讓咱們先從瀏覽一下 C 和 C++ 應用程序的整個代碼開始,而後對二者進行比較。性能
Sample2.c 是一個帶有嵌入式 JVM 的簡單的 C 應用程序:
#include <jni.h> #ifdef _WIN32 #define PATH_SEPARATOR ';' #else #define PATH_SEPARATOR ':' #endif int main() { JavaVMOption options[1]; JNIEnv *env; JavaVM *jvm; JavaVMInitArgs vm_args; long status; jclass cls; jmethodID mid; jint square; jboolean not; options[0].optionString = "-Djava.class.path=."; memset(&vm_args, 0, sizeof(vm_args)); vm_args.version = JNI_VERSION_1_2; vm_args.nOptions = 1; vm_args.options = options; status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); if (status != JNI_ERR) { cls = (*env)->FindClass(env, "Sample2"); if(cls !=0) { mid = (*env)->GetStaticMethodID(env, cls, "intMethod", "(I)I"); if(mid !=0) { square = (*env)->CallStaticIntMethod(env, cls, mid, 5); printf("Result of intMethod: %d\n", square); } mid = (*env)->GetStaticMethodID(env, cls, "booleanMethod", "(Z)Z"); if(mid !=0) { not = (*env)->CallStaticBooleanMethod(env, cls, mid, 1); printf("Result of booleanMethod: %d\n", not); } } (*jvm)->DestroyJavaVM(jvm); return 0; } else return -1; }
Sample2.cpp 是一個帶有嵌入式 JVM 的 C++ 應用程序:
#include <jni.h> #ifdef _WIN32 #define PATH_SEPARATOR ';' #else #define PATH_SEPARATOR ':' #endif int main() { JavaVMOption options[1]; JNIEnv *env; JavaVM *jvm; JavaVMInitArgs vm_args; long status; jclass cls; jmethodID mid; jint square; jboolean not; options[0].optionString = "-Djava.class.path=."; memset(&vm_args, 0, sizeof(vm_args)); vm_args.version = JNI_VERSION_1_2; vm_args.nOptions = 1; vm_args.options = options; status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); if (status != JNI_ERR) { cls = (*env)->FindClass(env, "Sample2"); if(cls !=0) { mid = (*env)->GetStaticMethodID(env, cls, "intMethod", "(I)I"); if(mid !=0) { square = (*env)->CallStaticIntMethod(env, cls, mid, 5); printf("Result of intMethod: %d\n", square); } mid = (*env)->GetStaticMethodID(env, cls, "booleanMethod", "(Z)Z"); if(mid !=0) { not = (*env)->CallStaticBooleanMethod(env, cls, mid, 1); printf("Result of booleanMethod: %d\n", not); } } (*jvm)->DestroyJavaVM(jvm); return 0; } else return -1; }
C 和 C++ 代碼幾乎相同;惟一的差別在於用來訪問 JNI 函數的方法。在 C 中,爲了取出函數指針所引用的值,JNI 函數調用前要加一個 (*env)->
前綴。在 C++ 中,JNIEnv
類擁有處理函數指針查找的內聯成員函數。所以,雖然這兩行代碼訪問同一函數,但每種語言都有各自的語法,以下所示。
C 語法: | cls = (*env)->FindClass(env, "Sample2"); |
C++ 語法: | cls = env->FindClass("Sample2"); |
咱們剛纔編寫了許多代碼,但它們都作些什麼呢? 在執行步驟 4 以前,讓咱們更深刻地研究一下 C 應用程序的代碼。咱們將先瀏覽一些必要的步驟,包括準備本機應用程序以處理 Java 代碼、將 JVM 嵌入本機應用程序,而後從該應用程序內找到並調用 Java 方法。
咱們從 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
以前,必需爲它留出一些內存。一旦設置了內存,就能夠設置版本和選項參數了,以下所示:
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 了。先從調用方法開始:
JNI_CreateJavaVM(JavaVM **jvm, void** env, JavaVMInitArgs **vm_args)
若是成功,則這個方法返回零,不然,若是沒法建立 JVM,則返回 JNI_ERR
。
一旦建立了 JVM 以後,就能夠準備開始在本機應用程序中運行 Java 代碼。首先,須要使用 FindClass()
函數查找並裝入 Java 類,以下所示:
cls = (*env)->FindClass(env, "Sample2");
cls
變量存儲執行 FindClass()
函數後的結果。 若是找到該類,則 cls
變量表示該 Java 類的句柄。若是不能找到該類,則 cls
將爲零。
接下來,咱們但願用 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 方法,以下所示:
square = (*env)->CallStaticIntMethod(env, cls, mid, 5);
CallStaticIntMethod()
方法接受 cls
(表示類)、mid
(表示方法)以及用於該方法一個或多個參數。在本例中參數是 int
5。
您還會遇到 CallStaticXXXMethod()
和 CallXXXMethod()
之類的方法。這些方法分別調用靜態方法和成員方法,用方法的返回類型(例如,Object
、Boolean
、Byte
、Char
、Int
、Long
等等)代替變量 XXX
。
如今準備運行這個 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()
。儘管從 C 調用 Java 方法確實須要至關高級的「類面向對象編程」技術,但這對經驗豐富的 C 程序員而言相對比較簡單。儘管 JNI 支持 C 和 C++,但 C++ 接口要更清晰些,一般比 C 接口更可取。
要記住很重要的一點是:能夠用單個 JVM 來裝入和執行多個類和方法。若是每次從本機代碼與 Java 交互都建立和銷燬 JVM,則會浪費資源並下降性能。