JNI設計實踐之路

JNI設計實踐之路
作者:楊小華  
一、       前言
本文爲在 32 位 Windows 平臺上實現 Java 本地方法提供了實用的示例、步驟和準則。本文中的示例使用 Sun公司的 Java Development Kit (JDK) 版本 1.4.2。 用 C ++語言編寫的本地代碼是用 Microsoft Visual C++  6.0編譯器編譯生成。規定在Java程序中function/method稱爲方法,在C++程序中稱爲函數。
本文將圍繞求圓面積逐步展開,探討java程序如何調用現有的DLL?如何在C++程序中創建,檢查及更新Java對象?如何在C++Java程序中互拋異常,並進行異常處理?最後將探討EclipseJBuilder工具可執行文件爲什麼不到100K大小以及所採用的技術方案?
二、       JNI基礎知識簡介
Java語言及其標準API應付應用程序的編寫已綽綽有餘。但在某些情況下,還是必須使用非Java代碼,例如:打印、圖像轉換、訪問硬件、訪問現有的非Java代碼等。與非Java代碼的溝通要求獲得編譯器和JVM的專門支持,並需附加的工具將Java代碼映射成非Java代碼。目前,不同的開發商爲我們提供了不同的方案,主要有以下方法:
1.         JNIJava Native Interface
2.         JRIJava Runtime Interface
3.         J/Direct
4.         RNIRaw Native Interface
5.         Java/COM集成方案
6.         CORBACommon Object Request Broker Architecture
其中方案1JDK自帶的一部分,方案2由網景公司所提供,方案3 4 5是微軟所提供的方案,方案6是一家非盈利組織開發的一種集成技術,利用它可以在由不同語言實現的對象之間進行“相互操作”的能力。
在開發過程中,我們一般採用第1種方案――JNI技術。因爲只用當程序用Microsoft Java編譯器編譯,而且只有在Microsoft Java虛擬機(JVM)上運行的時候,才採用方案3 4 5。而方案6一般應用在大型的分佈式應用中。
       JNI是一種包容極廣的編程接口,允許我們從Java應用程序裏調用本地化方法。也就是說,JNI允許運行在虛擬機上的Java程序能夠與其它語言(例如C/ C++/彙編語言)編寫的程序或者類庫進行相互間的調用。同時JNI也提供了一整套的API,允許將Java虛擬機直接嵌入到本地的應用程序中。其中JNI所扮演的角色可用圖一描述:
 
圖一 JNI基本結構描述圖
目前JNI只能與用CC++編寫的本地化方法打交道。利用JNI,我們本地化方法可以:
1.         創建、檢查及更新Java對象
2.         調用Java和非Java程序所編寫的方法(函數),以及win32 API.
3.         捕獲和拋出“異常”
4.         裝載類並獲取類信息
5.         進行運行期類型檢查
所以,原來在Java程序中能對類及對象所做的幾乎所有事情都可以在本地化方法中實現。
下圖表示了通過JNIJava程序和非Java程序相互調用原理。
圖二   Java程序和非Java程序通過JNI相互調用原理
       通過JNI,編寫Java程序調用非Java程序一般步驟:
1.)    編寫對本地化方法及自變量進行聲明的Java代碼
2.)    利用頭文件生成器javah生成本地化方法對應的頭文件
3.)    利用CC++實現本地化方法(可調用非Java程序),並編譯、鏈接生成DLL文件
4.)    Java程序通過生成的DLL調用非Java程序
     同時我們也可以通過JNIJava虛擬機直接嵌入到本地的應用程序中,步驟很簡單,只需要在C/C++程序中以JNI API函數爲媒介調用Java程序。
以上步驟雖簡單,但有很多地方值得注意。如果一招不慎,可能造成滿盤皆輸。
三、       Java程序調用非Java程序
3.1 本地化方法聲明及頭文件生成
任務:現有一求圓面積的Circle.dll(用MFC編寫,參數:圓半徑返回值:圓面積)文件,在Java程序中調用該Dll
在本地化聲明中,可分無包和有包兩種情況。我們主要對有包的情況展開討論。
實例1
package com.testJni;
public class Circle
{
   public native void cAreas(int radius) ;
   static
   {
        //System.out.println(System.getProperty("java.library.path"));
        System.loadLibrary("CCircle");
    }
}
Java程序中,需要在類中聲明所調用的庫名稱System.loadLibrary( String libname );
該函數是將一個Dll/so庫載入內存,並建立同它的鏈接。定位庫的操作依賴於具體的操作系統。在windows下,首先從當前目錄查找,然後再搜尋”PATH”環境變量列出的目錄。如果找不到該庫,則會拋出異常UnsatisfiedLinkError。庫的擴展名可以不用寫出來,究竟是Dll還是so,由系統自己判斷。這裏加載的是3.2中生成的DLL,而不是其他應用程序生成的Dll還需要對將要調用的方法做本地聲明,關鍵字爲native。表明此方法在本地方法中實現,而不是在Java程序中,有點類似於關鍵字abstract
    我們寫一個Circle.bat批處理文件編譯Circle.java文件,內容如下(可以用其他工具編譯)
javac -d . Circle.java
javah com.testJni.Circle
pause
對於有包的情況一定要注意這一點,就是在用javah時有所不同。開始時我的程序始終運行都不成功,問題就出在這裏。本類名稱的前面均是包名。這樣生成的頭文件就是:com_testJni_Circle.h開始時,在包含包的情況下我用javah Circle生成的頭文件始終是Circle.h。在網上查資料時,看見別人的頭文件名砸那長,我的那短。但不知道爲什麼,現在大家和我一樣知道爲什麼了吧。:)。
如果是無包的情況,則將批處理文件換成如下內容:
javac Circle.java
javah Circle
pause
3.2 本地化方法實現
剛纔生成的com_testJni_Circle.h頭文件內容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_testJni_Circle */
#ifndef _Included_com_testJni_Circle
#define _Included_com_testJni_Circle
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_testJni_Circle
 * Method:    cAreas
 * Signature:  (I)V
 */
JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas
 (JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
#endif
#endif
如果在本地化方法聲明中,方法cAreas ()聲明爲static類型,則與之相對應的Java_com_testJni_Circle_cAreas()函數中的第二個參數類型爲jclass。也就是
JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *env, jclass newCircle,jint radius)
這裏JNIEXPORTJNICALL都是JNI的關鍵字,其實是一些宏(具體參看jni_md.h文件)
從以上頭文件中,可以看出函數名生成規則爲:Java[ _包名]_類名_方法名[ _函數簽名](其中[ ]是可選項),均以字符下劃線( _ )分割。如果是無包的情況,則不包含[ _包名]選項。如果本地化方法中有方法重載,則在該函數名最後面追加函數簽名,也就是Signature對應的值,函數簽名參見表一。
 
函數簽名
Java類型
V
void
Z
boolean
B
byte
C
char
S
short
I
int
J
long
F
float
D
double
L fully-qualified-class ;
fully-qualified-class
[ type
type[]
( arg-types ) ret-type
method type
表一函數簽名與Java類型的映射
在具體實現的時候,我們只關心函數原型: 
JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *, jobject, jint);
現在就讓我們開始激動人心的一步吧 : ) 。啓動VC集成開發環境,新建一工程,在project裏選擇win32 Dynamic-link Library,輸入工程名,然後點擊ok,接下去步驟均取默認(圖三)。如果不取默認,生成的工程將會有DllMain ()函數,反之將無這個函數。我在這裏取的是空。
圖三   新建DLL工程
然後選擇菜單File->new->Files->C++ Source File,生成一個空*.cpp文件,取名爲CCircle。與3.1System.loadLibrary("CCircle");參數保持一致。將JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *, jobject, jint);拷貝到CPP文件中,幷包含其頭文件。
對應的CCircle.cpp內容如下:
#include<iostream.h>
#include"com_testJni_Circle.h"
#include"windows.h"
 
JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *env, jobject newCircle,jint radius)
{
     //調用求圓面積的Circle.dll
     typedef void (*PCircle)(int radius);
     HINSTANCE hDLL;
     PCircle Circle;
     hDLL=LoadLibrary("Circle.dll");//加載動態鏈接庫Circle.dll文件
     Circle=(PCircle)GetProcAddress(hDLL,"Circle");
     Circle(8);
     FreeLibrary(hDLL);//卸載Circle.dll文件;
}
 
在編譯前一定要注意下列情況。
注意:一定要把SDK目錄下include文件夾及其下面的win32文件夾中的頭文件拷貝到VC目錄的include文件夾下。或者在VCtools/options/directories中設置,如圖四所示。
圖四   頭文件設置
我們知道dll文件有兩種指明導出函數的方法,一種是在.def文件中定義,另一種是在定義函數時使用關鍵字__declspec(dllexport)。而關鍵字JNIEXPORT實際在jni_md.h中如下定義,#define JNIEXPORT __declspec(dllexport),可見JNI默認的導出函數使用第二種。使用第二種方式產生的導出函數名會根據編譯器發生變化,在有的情況下會發生找不到導出函數的問題(我們在java控制檯程序中調用很正常,但把它移植到JSP頁面時,就發生了該問題,JVM開始崩潰,百思不得其解,後來加入一個.def文件才解決問題)。其實在《windows 核心編程》一書中,第19.3.2節就明確指出創建用於非Visual C++工具的DLL時,建議加入一個def文件,告訴Microsoft編譯器輸出沒有經過改變的函數名。因此最好採用第一種方法,定義一個.def文件來指明導出函數。本例中可以新建一個CCircle.def文件,內容如下:
; CCircle.def : Declares the module parameters for the DLL.
 
LIBRARY      "CCircle"
DESCRIPTION 'CCircle Windows Dynamic Link Library'
 
EXPORTS
    ; Explicit exports can go here
    Java_com_testJni_Circle_cAreas
現在開始對所寫的程序進行編譯。選擇build->rebuild all對所寫的程序進行編譯。點擊build->build CCirclee.DLL生成DLL文件。
也可以用命令行cl來編譯。語法格式參見JDK文檔JNI部分。
再次強調(曾經爲這個東西大傷腦筋)DLL放置地方
1)        當前目錄。
2)        Windows的系統目錄及Windows目錄
3)        放在path所指的路徑中
4)        自己在path環境變量中設置一個路徑,要注意所指引的路徑應該到.dll文件的上一級,如果指到.dll,則會報錯。
下面就開始測試我們的所寫的DLL吧(假設DLL已放置正確)。
import com.testJni.Circle;
public class test
{
   public static void main(String argvs[])
   {
      Circle myCircle;
      myCircle = new Circle();
      myCircle.cAreas(2);
   }     
}
編譯,運行程序,將會彈出如下界面:

圖五 運行結果
以上是我們通過JNI方法調用的一個簡單程序。實際情況要比這複雜的多。
現在開始來討論JNI中參數的情況,我們來看一個程序片斷。
實例二:
JNIEXPORT jstring JNICALL Java_MyNative_cToJava
(JNIEnv *env, jclass obj)
{
              jstring jstr;
              char str[]="Hello,word!/n";
              jstr=env->NewStringUTF(str);
              return jstr;
}
CJava編程語言之間傳送值時,需要理解這些值類型在這兩種語言間的對應關係。這些都在頭文件jni.h中定義,用typedef語句聲明瞭這些類在目標平臺上的代價類。頭文件也定義了常量如:JNI_FALSE=0 JNI_TRUE=1;表二和表三說明了Java類型和C類型之間的映射關係。
Java語言
C/C++語言
bit位數
boolean
jboolean
8 unsigned
byte
jbyte
8
char
jchar
16 unsigned
short
jshort
16
int
jint
32
long
jlong
64
float
jfloat
32
double
jdouble
64
void
void
0
表二   Java基本類型到本地類型的映射
表三 Java中的類到本地類的映射
JNI函數NewStringUTF()是從一個包含UTF格式編碼字符的char類型數組中創建一個新的jstring對象。jstring是以JNI爲中介使JavaString類型與本地的string溝通的一種類型,我們可以視而不見 (具體對應見表二和表三)如果你使用的函數是GetStringUTFChars()(jstring轉換爲UTF-8字符串),必須同時使用ReleaseStringUTFChars()函數,通過它來通知虛擬機去回收UTF-8串佔用的內存,否則將會造成內存泄漏,最終導致系統崩潰。因爲JVM在調用本地方法時,是在虛擬機中開闢了一塊本地方法棧供本地方法使用,當本地方法使用完UTF-8串後,得釋放所佔用的內存。其中程序片斷jstr=env->NewStringUTF(str);C++中的寫法,不必使用env指針。因爲JNIEnv函數的C++版本包含有直接插入成員函數,他們負責查找函數指針。而對於C的寫法,應改爲:jstr=(*env)->NewStringUTF(env,str);因爲所有JNI函數的調用都使用env指針,它是任意一個本地方法的第一個參數。env指針是指向一個函數指針表的指針。因此在每個JNI函數訪問前加前綴(*env)->以確保間接引用函數指針。
C/C++Java互傳參數需要自己在編程過程中仔細摸索與體味。
四、       C/C++訪問Java成員變量和成員方法
我們修改3.1中的Java程序聲明,加入如下代碼:
   private int circleRadius;
   public Circle()
   {
       circleRadius=0;
   }
   public void setCircleRadius(int radius)
   {
       circleRadius=radius;
   }
   public void javaAreas()
   {
       float PI = 3.14f;
       if(circleRadius<=0)
       {
           System.out.println (“error!”);
       }
       else
       {
          System.out.println (PI*circleRadius*circleRadius);
       }      
    }
C++程序中訪問Circle類中的private私有成員變量circleRadius,並設置它的值,同時調用Java方法javaAreas()。在函數Java_com_testJni_Circle_cAreas()中加入如下代碼:
jclass circle;
       jmethodID AreasID;
       jfieldID radiusID;
jint newRadius=5;
       circle = env->GetObjectClass(newCircle);//get current class
       radiusID=env->GetFieldID(circle,"circleRadius","I");//get field ID
       env->SetIntField(newCircle,radiusID,newRadius);//set field value
       AreasID=env->GetMethodID(circle,"javaAreas","()V");//get method ID
       env->CallVoidMethod(newCircle,AreasID,NULL);//invoking method
C++代碼中,創建、檢查及更新Java對象,首先要得到該類,然後再根據類得到其成員的ID,最後根據該類的對象,ID號調用成員變量或者成員方法。
得到類,有兩個API函數,分別爲FindClass()GetObjectClass();後者顧名思義用於已經明確知道其對象,然後根據對象找類。前者用於得到沒有實例對象的類。這裏也可以改成circle = env-> FidnClass("com/testJni/Circle");其中包的分隔符用字符" /"代替。如果已知一個類,也可以在C++代碼中創建該類對象,其JNI函數爲NewObject();示例代碼如下:
jclass      circle =env->FindClass("com/testJni/ Circle ");
jmethodID circleID=env->GetMethodID(circle,"<init>","()V");//得到構造函數的ID
jobject newException=env->NewObject(circle, circleID,NULL);
得到成員變量的ID,根據其在Java代碼中聲明的類型不同而不同。具體分爲兩大類:非static型和static型,分別對應GetFieldID()GetStaticFieldID()。同時也可以獲得和設置成員變量的值,根據其聲明的type而變化,獲得其值的API函數爲:GettypeField()GetStatictypeField();與之相對應的設置其值的函數爲SettypeField()SetStatictypeField();在本例中,成員變量circleRadius聲明成int型,則對應的函數分別爲GetIntField()SetIntField();
其實JNI API函數名是很有規律的,從上面已窺全貌。獲得成員方法的ID也是同樣的分類方法。具體爲GetMethodID()GetStaticMethodID()。調用成員方法跟獲得成員變量的值相類似,也根據其方法返回值的type不同而不同,分別爲CalltypeMethod()CallStatictypeMethod()。對於返回值爲void的類型,其相應JNI函數爲CallVoidMethod()
以上獲得成員ID函數的形參均一致。第一個參數爲jclass,第二個參數爲成員變量或方法,第三個參數爲該成員的簽名(簽名可參見表一)。但調用或設置成員變量或方法時,第一個參數爲實例對象(jobject),其餘形參與上面相同。
特別要注意的是得到構造方法的ID時,第二個參數不遵循上面的原則,爲jmethodID constructorID = env->GetMethodID(jclass, "<init>"," 函數簽名");
從上面代碼中可以看出,在C++中可以訪問java程序private類型的變量,嚴重破壞了類的封裝原則。從而可以看出其不安全性。
五、       異常處理
本地化方法穩定性非常差,調用任何一個JNI函數都會出錯,爲了程序的健壯性,非常有必要在本地化方法中加入異常處理。我們繼續修改上面的類。
我們聲明一個異常類,其代碼如下:
package com.testJni;
import com.testJni.*;
public class RadiusIllegal extends Exception
{
    protected String MSG="error!";
    public RadiusIllegal(String message)
    {
        MSG=message;
    }
    public void print()
    {
       System.out.println(MSG);
    }
}
同時也修改Circle.java中的方法,加入異常處理。
    public void javaAreas() throws RadiusIllegal    //修改javaAreas(),加入異常處理
   {
       float PI = 3.14f;
       if(circleRadius<=0)
       {
           throw new RadiusIllegal("warning:radius is illegal!");
       }
       else
       {
            System.out.println (PI*circleRadius*circleRadius);
       }      
   }
   public native void cAreas(int radius) throws RadiusIllegal;    //修改cAreas (),加入異常處理
   修改C++代碼中的函數,加入異常處理,實現JavaC++互拋異常,並進行異常處理。
   JNIEXPORT void JNICALL Java_com_testJni_Circle_cAreas(JNIEnv *env, jobject newCircle,jint radius)
{
       //此處省略部分代碼
       radiusIllegal=env->FindClass("com/testJni/RadiusIllegal");//get the exception class
       if((exception=env->ExceptionOccurred())!=NULL)
       {
              cout<<"errors in com_testJni_RadiusIllegal"<<endl;
              env->ExceptionClear();
       }    
       //此處省略部分代碼
       env->CallVoidMethod(newCircle,AreasID,NULL);//invoking
       if((exception=env->ExceptionOccurred())!=NULL)
       {
              if(env->IsInstanceOf(exception,radiusIllegal)==JNI_TRUE)
              {                  
                     cout<<"errors in java method"<<endl;
                     env->ExceptionClear();
              }
              else
              {
                     cout<<"errors in invoking javaAreas() method of Circle"<<endl;
                     env->ExceptionClear();
              }
        }   
        if(radius<=0)
        {
              env->ThrowNew(radiusIllegal,"errors in C function!");//throw exception
         return ;
        }
        else
        {
              //此處爲調用計算圓面積的DLL
        }
}
在本地化方法(C++)中,可以自己處理異常,也可以重新拋出異常,讓Java程序來捕獲該異常,進行相關處理。
如果調用JNI函數發生異常,不及時進行處理,再次調用其他JNI函數時,可能會使JVM崩潰(crash),
大多數JNI函數都具有此特性。可以調用函數ExceptionOccurred()來判斷是否發生了異常。該函數返回jthrowable的實例對象,如本例if((exception=env->ExceptionOccurred())!=NULL)就用來判斷是否發生了異常。當要判斷具體是哪個異常發生時,可以用IsInstanceOf()來進行測試,此函數非彼IsInstanceOf(Java語言中的IsInstanceOf)。在上面的代碼中,我們在本地化方法中給circleRadius設置了一非法值,然後調用方法javaAreas(),此時java代碼會拋出異常,在本地化方法中進行捕獲,然後用IsInstanceOf()來進行測試是否發生了RadiusIllegal類型的異常,以便進行相關處理。在調用其他JNI函數之前,應當首先清除異常,其函數爲ExceptionClear()
如果是C++的程序發生異常,則可以用JNI API函數ThrowNew()拋出該異常。但此時本地化方法並不返回退出,直到該程序執行完畢。所以當在本地化方法中發生異常時,應該人爲的退出,及時進行處理,避免程序崩潰。函數ThrowNew()中第一個參數爲jclass的類,第二個參數爲附加信息,用來描述異常信息。
如果要知道異常發生的詳細信息,或者對程序進行調試時,可以用函數ExceptionDescribe()來顯示異常棧裏面的內容。
 
六、       MFC程序中嵌入Java虛擬機
可能大家天天都在用EclipseJbulider這兩款優秀的IDE進行程序開發,可能還不知道他們的可執行文件不到100KB大小,甚則連一副圖片都可能比他們大。其實隱藏在他們背後的技術是JNI,可執行文件只是去啓動Java程序,所以也只有那麼小。
我們只需要在MFC程序中創建一個JVM,然後基於這個JVM調用Java的方法,啓動Java程序,就可以模擬出EclipseJbulider的那種效果,使java程序更專業。其實要實現這種效果,用上面的方法足有夠有。創建JVM,只需包含相應的類庫,設置相關的屬性。
首先進行環境設置,VC環境的tools-->options-->Directories下的Library files選項中包含其創建JVM的庫文件jvm.lib,該庫文件位於JDK / lib目錄下,如圖6所示:
圖六庫文件路徑設置
然後,在環境變量path中設置jvm.dll的路徑。該Dll      位於jdk/jre/bin/server目錄或jdk/jre/bin/client目錄下。注意:一定不要將jvm.dlljvm.lib拷貝到你應用程序路徑下,這樣會引起JVM初始化失敗。因爲Java虛擬機是以相對路徑來尋找和調用用到的庫文件和其他相關文件。
接下來,我們在MFC程序(該程序請到《程序員》雜誌頻道下載)中進行創建JVM初始化工作。示例代碼如下:
       JNIEnv *env;
       JavaVM *jvm;
       jint res;
       jclass cls;
       jmethodID mid;      
       JavaVMInitArgs vm_args;
       JavaVMOption options[3];
       memset(&vm_args, 0, sizeof(vm_args));    
     //進行初始化工作      
       options[0].optionString = "-Djava.compiler=NONE";
       options[1].optionString = "-Djava.class.path=.";
       options[2].optionString = "-verbose:jni";      
       vm_args.version=JNI_VERSION_1_4;       //版本號設置
       vm_args.nOptions = 3;
       vm_args.options = options;
       vm_args.ignoreUnrecognized = JNI_TRUE;
 
       res = JNI_CreateJavaVM(&jvm,(void**)&env,&vm_args); //創建JVM
       if (res < 0)
       {
              MessageBox( "Can't create Java VM","Error",MB_OK|MB_ICONERROR);
              exit(1);
       }
       cls = env->FindClass("prog");
       if(env->ExceptionOccurred()!=NULL)
       {
              MessageBox( "Can't find Prog class!","Error",MB_OK|MB_ICONERROR);
              exit(1);
       }
       mid = env->GetStaticMethodID(cls, "main", "([Ljava/lang/String;)V");
       if(env->ExceptionOccurred()!=NULL)
       {
              MessageBox("Can't find Prog.main!","Error",MB_OK|MB_ICONERROR);
              exit(1);
       }
       env->CallStaticVoidMethod( cls, mid, NULL); //調用Java程序main()方法,啓動Java程序
       if(env->ExceptionOccurred()!=NULL)
       {
              MessageBox( "Fatal Error!","Error",MB_OK|MB_ICONERROR);
              exit(1);
       }
       jvm->DestroyJavaVM();//釋放JVM資源
程序首先進行JVM初始化設置。我們觀察jni.h 文件關於JavaVMOptionJavaVMInitArgs的定義
typedef struct JavaVMOption {
    char *optionString;
    void *extraInfo;
} JavaVMOption;
 
typedef struct JavaVMInitArgs {
    jint version;
    jint nOptions;
    JavaVMOption *options;
    jboolean ignoreUnrecognized;
} JavaVMInitArgs;
結構體JavaVMInitArgs中有四個參數,我們在程序中都得必須設置。其中版本號一定要設置正確,不同的版本有不同的設置方法,關於版本1.11.2的設置方法參看sun公司的文檔,這裏只給出版本1.4的設置方法。第二個參數表示JavaVMOption結構體變量的維數,這裏設置爲三維,其中options[0].optionString = "-Djava.compiler=NONE";表示disable JIT;options[1].optionString = "-Djava.class.path=.";表示你所調用Java程序的Class文件的路徑,這裏設置爲該exe應用程序的根路徑(最後一個字符"."表示根路徑)options[2].optionString = "-verbose:jni";用於跟蹤運行時的信息。第三個參數是一個JavaVMOption的指針變量。第四個參數意思我們可以參看幫助文檔的解釋If ignoreUnrecognized is JNI_FALSE, JNI_CreateJavaVM returns JNI_ERR as soon as it encounters any unrecognized option strings
初始化完畢後,就可以調用創建JVM的函數jint JNICALL JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);如果返回值小於0表示創建JVM失敗。最可能的原因就是jvm.dlljvm.lib設置錯誤。
如果在運行的過程中找不到java程序的類,那麼就是-Djava.class.path設置錯誤。只要JVM創建成功,就可以根據上面的方法調用java程序。最後當程序結束後,調用函數DestroyJavaVM()摧毀JVM,釋放資源。
七、       附錄
利用JNI函數,我們可以從本地化方法的內部與JVM打交道。正如在前面的例子中所看到的那樣,每個本地化方法中都會接收一個特殊的自變量作爲自己的第一個參數:JNIEnv――它是指向類型爲JNIEnv_的一個特殊JNI數據結構的指針。JNI數據結構的一個元素是指向由JVM生成的一個指針的數組;該數組的每個元素都是指向一個JNI函數的指針。可以從本地化方法的內部對JNI函數的調用。第二個參數會根據Java類中本地方法的定義不同而不同,如果是定義爲static方法,類型會是jclass,表示對特定Class對象的引用,如果是非static方法,類型是jobject,表示當前對象的引用,相當於 this可以說這兩個變量是本地化方法返回JAVA的大門。
注意:在本地化方法中生成的Dll不具備到處運行的特性,而具有牽一髮而動全身的特點。只要包名一改變,那麼你所有的工作就得重新做一遍。原因就是當用javah生成頭文件時,函數名的生成規則爲Java[ _包名]_類名_方法名[ _函數簽名];當你的包名改變時,生成的函數名也跟着改變了,那麼你再次調用以前編寫的Dll時,會拋出異常。
 
八、       參考文獻
1.         Java 編程思想》Bruce Eckel 機械工業出版社
2.         Java2 核心技術2》(第6版)Cay S.Horstmann,Gary Cornell機械工業出版社
3.         《高級Java2 大學教程》(英文版) Harvey M.Deitel ,Paul J.Deitel,Sean E.Santry 電子工業出版社
4.         windows 核心編程》Jeffrey Richter 機械工業出版社
5.           http://www.jguru.com
6.                    sun公司文檔
 
如對本文有任何疑問和異議,歡迎與作者探討:[email protected]
 
注:本文最初發表在2004年《開發高手》第12期上。
 
發佈了38 篇原創文章 · 獲贊 17 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章