用 JNI 進行 Java 編程(3)

從 C/C++ 程序調用 Java 代碼java

概述程序員

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

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

  • 須要在本機應用程序中訪問用 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++ 代碼使用的功能。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

步驟 2:編譯 Java 代碼

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


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 應用程序:


#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;
}

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

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++ 實現的比較

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,則會浪費資源並下降性能。

相關文章
相關標籤/搜索