codefans

          導航

          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          統(tǒng)計

          常用鏈接

          留言簿(2)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          程序設計鏈接

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          如何在c,c++中加入java

          Java跨平臺的特性使Java越來越受開發(fā)人員的歡迎,但也往往會聽到不少的抱怨:用Java
          開發(fā)的圖形用戶窗口界面每次在啟動的時候都會跳出一個控制臺窗口,這個控制臺窗口讓
          本來非常棒的界面失色不少。怎么能夠讓通過Java開發(fā)的GUI程序不彈出Java的控制臺窗口
          呢?其實現(xiàn)在很多流行的開發(fā)環(huán)境例如JBuilder、Eclipse都是使用純Java開發(fā)的集成環(huán)境
          。這些集成環(huán)境啟動的時候并不會打開一個命令窗口,因為它使用了JNI(Java Native I
          nterface)的技術。通過這種技術,開發(fā)人員不一定要用命令行來啟動Java程序,可以通
          過編寫一個本地GUI程序直接啟動Java程序,這樣就可避免另外打開一個命令窗口,讓開發(fā)
          的Java程序更加專業(yè)。 



          JNI允許運行在虛擬機的Java程序能夠與其它語言(例如C和C++)編寫的程序或者類庫進行
          相互間的調用。同時JNI提供的一整套的API,允許將Java虛擬機直接嵌入到本地的應用程
          序中。圖1是Sun站點上對JNI的基本結構的描述。 


          圖1 JNI基本結構描述圖





          本文將介紹如何在C/C++中調用Java方法,并結合可能涉及到的問題介紹整個開發(fā)的步驟及
          可能遇到的難題和解決方法。本文所采用的工具是Sun公司創(chuàng)建的 Java Development Kit
           (JDK) 版本 1.3.1,以及微軟公司的Visual C++ 6開發(fā)環(huán)境。 





          環(huán)境搭建





          為了讓本文以下部分的代碼能夠正常工作,我們必須建立一個完整的開發(fā)環(huán)境。首先需要
          下載并安裝JDK 1.3.1,其下載地址為“http://java.sun.com”。假設安裝路徑為C:\JD
          K。下一步就是設置集成開發(fā)環(huán)境,通過Visual C++ 6的菜單Tools→Options打開選項對話
          框如圖2。 


          圖2 設置集成開發(fā)環(huán)境圖


          將目錄C:\JDK\include和C:\JDK\include\win32加入到開發(fā)環(huán)境的Include Files目錄中,
          同時將C:\JDK\lib目錄添加到開發(fā)環(huán)境的Library Files目錄中。這三個目錄是JNI定義的
          一些常量、結構及方法的頭文件和庫文件。集成開發(fā)環(huán)境已經(jīng)設置完畢,同時為了執(zhí)行程
          序需要把Java虛擬機所用到的動態(tài)鏈接庫所在的目錄C:\JDK \jre\bin\classic設置到系統(tǒng)
          的Path環(huán)境變量中。這里需要提出的是,某些開發(fā)人員為了方便直接將JRE所用到的DLL文
          件直接拷貝到系統(tǒng)目錄下。這樣做是不行的,將導致初始化Java虛擬機環(huán)境失敗(返回值
          -1),原因是Java虛擬機是以相對路徑來尋找所用到的庫文件和其它一些相關文件的。至
          此整個JNI的開發(fā)環(huán)境設置完畢,為了讓此次JNI旅程能夠順利進行,還必須先準備一個Ja
          va類。在這個類中將用到Java中幾乎所有有代表性的屬性及方法,如靜態(tài)方法與屬性、數(shù)
          組、異常拋出與捕捉等。我們定義的Java程序(Demo.java)如下,本文中所有的代碼演示都
          將基于該Java程序,代碼如下: 



          package jni.test;

          /**

          * 該類是為了演示JNI如何訪問各種對象屬性等

          * @author liudong

          */

          public class Demo {

          //用于演示如何訪問靜態(tài)的基本類型屬性

          public static int COUNT = 8;

          //演示對象型屬性

          public String msg;

          private int[] counts;

          public Demo() {

          this("缺省構造函數(shù)");

          }

          /**

          * 演示如何訪問構造器

          */

          public Demo(String msg) {

          System.out.println(":" + msg);

          this.msg = msg;

          this.counts = null;

          }

          /**

          * 該方法演示如何訪問一個訪問以及中文字符的處理

          */

          public String getMessage() {

          return msg;

          }

          /**

          * 演示數(shù)組對象的訪問

          */

          public int[] getCounts() {

          return counts;

          }

          /**

          * 演示如何構造一個數(shù)組對象

          */

          public void setCounts(int[] counts) {

          this.counts = counts;

          }

          /**

          * 演示異常的捕捉

          */

          public void throwExcp() throws IllegalAccessException {

          throw new IllegalAccessException("exception occur.");

          }

          }









          初始化虛擬機





          本地代碼在調用Java方法之前必須先加載Java虛擬機,而后所有的Java程序都在虛擬機中
          執(zhí)行。為了初始化Java虛擬機,JNI提供了一系列的接口函數(shù)Invocation API。通過這些A
          PI可以很方便地將虛擬機加載到內存中。創(chuàng)建虛擬機可以用函數(shù) jint JNI_CreateJavaVM
          (JavaVM **pvm, void **penv, void *args)。對于這個函數(shù)有一點需要注意的是,在JDK
           1.1中第三個參數(shù)總是指向一個結構JDK1_ 1InitArgs, 這個結構無法完全在所有版本的
          虛擬機中進行無縫移植。在JDK 1.2中已經(jīng)使用了一個標準的初始化結構JavaVMInitArgs來
          替代JDK1_1InitArgs。下面我們分別給出兩種不同版本的示例代碼。 



          在JDK 1.1初始化虛擬機: 



          #include 

          int main() {

          JNIEnv *env;

          JavaVM *jvm;

          JDK1_1InitArgs vm_args;

          jint res;

          /* IMPORTANT: 版本號設置一定不能漏 */

          vm_args.version = 0x00010001;

          /*獲取缺省的虛擬機初始化參數(shù)*/

          JNI_GetDefaultJavaVMInitArgs(&vm_args);

          /* 添加自定義的類路徑 */

          sprintf(classpath, "%s%c%s",

          vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);

          vm_args.classpath = classpath;

          /*設置一些其他的初始化參數(shù)*/

          /* 創(chuàng)建虛擬機 */

          res = JNI_CreateJavaVM(&jvm,&env,&vm_args);

          if (res < 0) {

          fprintf(stderr, "Can't create Java VM\n");

          exit(1);

          }

          /*釋放虛擬機資源*/

          (*jvm)->DestroyJavaVM(jvm);

          }







          JDK 1.2初始化虛擬機: 



          /* invoke2.c */

          #include 

          int main() {

          int res;

          JavaVM *jvm;

          JNIEnv *env;

          JavaVMInitArgs vm_args;

          JavaVMOption options[3];

          vm_args.version=JNI_VERSION_1_2;//這個字段必須設置為該值

          /*設置初始化參數(shù)*/

          options[0].optionString = "-Djava.compiler=NONE"; 

          options[1].optionString = "-Djava.class.path=."; 

          options[2].optionString = "-verbose:jni"; //用于跟蹤運行時的信息

          /*版本號設置不能漏*/

          vm_args.version = JNI_VERSION_1_2;

          vm_args.nOptions = 3;

          vm_args.options = options;

          vm_args.ignoreUnrecognized = JNI_TRUE;

          res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

          if (res < 0) {

          fprintf(stderr, "Can't create Java VM\n");

          exit(1);



          (*jvm)->DestroyJavaVM(jvm);

          fprintf(stdout, "Java VM destory.\n");

          }







          為了保證JNI代碼的可移植性,建議使用JDK 1.2的方法來創(chuàng)建虛擬機。JNI_CreateJavaVM
          函數(shù)的第二個參數(shù)JNIEnv *env,就是貫穿整個JNI始末的一個參數(shù),因為幾乎所有的函數(shù)
          都要求一個參數(shù)就是JNIEnv *env。 





          訪問類方法





          初始化了Java虛擬機后,就可以開始調用Java的方法。要調用一個Java對象的方法必須經(jīng)
          過幾個步驟: 



          1.獲取指定對象的類定義(jclass) 



          有兩種途徑來獲取對象的類定義:第一種是在已知類名的情況下使用FindClass來查找對應
          的類。但是要注意類名并不同于平時寫的Java代碼,例如要得到類jni.test.Demo的定義必
          須調用如下代碼: 



          jclass cls = (*env)->FindClass(env, "jni/test/Demo"); //把點號換成斜杠


          然后通過對象直接得到其所對應的類定義: 
          jclass cls = (*env)-> GetObjectClass(env, obj);

          //其中obj是要引用的對象,類型是jobject







          2.讀取要調用方法的定義(jmethodID) 



          我們先來看看JNI中獲取方法定義的函數(shù): 



          jmethodID (JNICALL *GetMethodID)(JNIEnv *env, jclass clazz, const char *name, 
          const char *sig);

          jmethodID (JNICALL *GetStaticMethodID)(JNIEnv *env, jclass class, const char 
          *name, const char *sig);



          這兩個函數(shù)的區(qū)別在于GetStaticMethodID是用來獲取靜態(tài)方法的定義,GetMethodID則是
          獲取非靜態(tài)的方法定義。這兩個函數(shù)都需要提供四個參數(shù):env就是初始化虛擬機得到的J
          NI環(huán)境;第二個參數(shù)class是對象的類定義,也就是第一步得到的obj;第三個參數(shù)是方法
          名稱;最重要的是第四個參數(shù),這個參數(shù)是方法的定義。因為我們知道Java中允許方法的
          多態(tài),僅僅是通過方法名并沒有辦法定位到一個具體的方法,因此需要第四個參數(shù)來指定
          方法的具體定義。但是怎么利用一個字符串來表示方法的具體定義呢?JDK中已經(jīng)準備好一
          個反編譯工具javap,通過這個工具就可以得到類中每個屬性、方法的定義。下面就來看看
          jni.test.Demo的定義: 



          打開命令行窗口并運行 javap -s -p jni.test.Demo 得到運行結果如下: 





          Compiled from Demo.java

          public class jni.test.Demo extends java.lang.Object {

          public static int COUNT;

          /* I */

          public java.lang.String msg;

          /* Ljava/lang/String; */

          private int counts[];

          /* [I */

          public jni.test.Demo();

          /* ()V */

          public jni.test.Demo(java.lang.String);

          /* (Ljava/lang/String;)V */

          public java.lang.String getMessage();

          /* ()Ljava/lang/String; */

          public int getCounts()[];

          /* ()[I */

          public void setCounts(int[]);

          /* ([I)V */

          public void throwExcp() throws java.lang.IllegalAccessException;

          /* ()V */

          static {};

          /* ()V */

          }









          我們看到類中每個屬性和方法下面都有一段注釋。注釋中不包含空格的內容就是第四個參
          數(shù)要填的內容(關于javap具體參數(shù)請查詢JDK的使用幫助)。下面這段代碼演示如何訪問jn
          i.test.Demo的getMessage方法: 





          /*

          假設我們已經(jīng)有一個jni.test.Demo的實例obj 

          */

          jmethodID mid;

          jclass cls = (*env)-> GetObjectClass (env, obj); //獲取實例的類定義

          mid=(*env)->GetMethodID(env,cls,"getMessage"," ()Ljava/lang/String; ");

          /*如果mid為0表示獲取方法定義失敗*/

          jstring msg = (*env)-> CallObjectMethod(env, obj, mid);

          /*

          如果該方法是靜態(tài)的方法那只需要將最后一句代碼改為以下寫法即可:

          jstring msg = (*env)-> CallStaticObjectMethod(env, cls, mid);

          */









          3.調用方法 



          為了調用對象的某個方法,可以使用函數(shù)CallMethod或者CallStaticMethod(訪問類的靜
          態(tài)方法),根據(jù)不同的返回類型而定。這些方法都是使用可變參數(shù)的定義,如果訪問某個
          方法需要參數(shù)時,只需要把所有參數(shù)按照順序填寫到方法中就可以。在講到構造函數(shù)的訪
          問時,將演示如何訪問帶參數(shù)的構造函數(shù)。 





          訪問類屬性





          訪問類的屬性與訪問類的方法大體上是一致的,只不過是把方法變成屬性而已。 



          1.獲取指定對象的類(jclass) 



          這一步與訪問類方法的第一步完全相同,具體使用參看訪問類方法的第一步。 



          2.讀取類屬性的定義(jfieldID) 



          在JNI中是這樣定義獲取類屬性的方法的: 





          jfieldID (JNICALL *GetFieldID)

          (JNIEnv *env, jclass clazz, const char *name, const char *sig);

          jfieldID (JNICALL *GetStaticFieldID)

          (JNIEnv *env, jclass clazz, const char *name, const char *sig);


          這兩個函數(shù)中第一個參數(shù)為JNI環(huán)境;clazz為類的定義;name為屬性名稱;第四個參數(shù)同
          樣是為了表達屬性的類型。前面我們使用javap工具獲取類的詳細定義的時候有這樣兩行:
            public java.lang.String msg;

          /* Ljava/lang/String; */


          其中第二行注釋的內容就是第四個參數(shù)要填的信息,這跟訪問類方法時是相同的。 



          3.讀取和設置屬性值 



          有了屬性的定義要訪問屬性值就很容易了。有幾個方法用來讀取和設置類的屬性,它們是
          :GetField、SetField、GetStaticField、SetStaticField。比如讀取Demo類的msg屬性就
          可以用GetObjectField,而訪問COUNT用GetStaticIntField,相關代碼如下: 





          jfieldID field = (*env)->GetFieldID(env,obj,"msg"," Ljava/lang/String;");

          jstring msg = (*env)-> GetObjectField(env, cls, field); //msg就是對應Demo的msg


          jfieldID field2 = (*env)->GetStaticFieldID(env,obj,"COUNT","I");

          jint count = (*env)->GetStaticIntField(env,cls,field2);











          訪問構造函數(shù)





          很多人剛剛接觸JNI的時候往往會在這一節(jié)遇到問題,查遍了整個jni.h看到這樣一個函數(shù)
          NewObject,它應該是可以用來訪問類的構造函數(shù)。但是該函數(shù)需要提供構造函數(shù)的方法定
          義,其類型是jmethodID。從前面的內容我們知道要獲取方法的定義首先要知道方法的名稱
          ,但是構造函數(shù)的名稱怎么來填寫呢?其實訪問構造函數(shù)與訪問一個普通的類方法大體上
          是一樣的,惟一不同的只是方法名稱不同及方法調用時不同而已。訪問類的構造函數(shù)時方
          法名必須填寫“”(注, 我使用的時候發(fā)現(xiàn)應該是設置為NULL)。下面的代碼演示如何構造一個Demo類的實例: 





          jclass cls = (*env)->FindClass(env, "jni/test/Demo");

          /** 

          首先通過類的名稱獲取類的定義,相當于Java中的Class.forName方法

          */

          if (cls == 0) 



          jmethodID mid = (*env)->GetMethodID(env,cls,"","(Ljava/lang/String;)V ");

          if(mid == 0)



          jobject demo = jenv->NewObject(cls,mid,0);

          /**

          訪問構造函數(shù)必須使用NewObject的函數(shù)來調用前面獲取的構造函數(shù)的定義

          上面的代碼我們構造了一個Demo的實例并傳一個空串null

          */


          數(shù)組處理

          創(chuàng)建一個新數(shù)組 
          要創(chuàng)建一個數(shù)組,我們首先應該知道數(shù)組元素的類型及數(shù)組長度。JNI定義了一批數(shù)組的類
          型jArray及數(shù)組操作的函數(shù)NewArray,其中就是數(shù)組中元素的類型。例如,要創(chuàng)建一個大
          小為10并且每個位置值分別為1-10的整數(shù)數(shù)組,編寫代碼如下: 





          int i = 1;

          jintArray array; //定義數(shù)組對象

          (*env)-> NewIntArray(env, 10);

          for(; i<= 10; i++) 

          (*env)->SetIntArrayRegion(env, array, i-1, 1, &i);









          訪問數(shù)組中的數(shù)據(jù) 



          訪問數(shù)組首先應該知道數(shù)組的長度及元素的類型。現(xiàn)在我們把創(chuàng)建的數(shù)組中的每個元素值
          打印出來,代碼如下: 





          int i;

          /* 獲取數(shù)組對象的元素個數(shù) */

          int len = (*env)->GetArrayLength(env, array);

          /* 獲取數(shù)組中的所有元素 */

          jint* elems = (*env)-> GetIntArrayElements(env, array, 0);

          for(i=0; i< len; i++)

          printf("ELEMENT %d IS %d\n", i, elems[i]);





          中文處理

          中文字符的處理往往是讓人比較頭疼的事情,特別是使用Java語言開發(fā)的軟件,在JNI這個
          問題更加突出。由于Java中所有的字符都是Unicode編碼,但是在本地方法中,例如用VC編
          寫的程序,如果沒有特殊的定義一般都沒有使用Unicode的編碼方式。為了讓本地方法能夠
          訪問Java中定義的中文字符及Java訪問本地方法產(chǎn)生的中文字符串,我定義了兩個方法用
          來做相互轉換。 



          · 方法一,將Java中文字符串轉為本地字符串 



          /**

          第一個參數(shù)是虛擬機的環(huán)境指針

          第二個參數(shù)為待轉換的Java字符串定義

          第三個參數(shù)是本地存儲轉換后字符串的內存塊

          第三個參數(shù)是內存塊的大小

          */

          int JStringToChar(JNIEnv *env, jstring str, LPTSTR desc, int desc_len)

          {

          int len = 0;

          if(desc==NULL||str==NULL)

          return -1;

          //在VC中wchar_t是用來存儲寬字節(jié)字符(UNICODE)的數(shù)據(jù)類型

          wchar_t *w_buffer = new wchar_t[1024];

          ZeroMemory(w_buffer,1024*sizeof(wchar_t));

          //使用GetStringChars而不是GetStringUTFChars

          wcscpy(w_buffer,env->GetStringChars(str,0));

          env->ReleaseStringChars(str,w_buffer);

          ZeroMemory(desc,desc_len);

          //調用字符編碼轉換函數(shù)(Win32 API)將UNICODE轉為ASCII編碼格式字符串

          //關于函數(shù)WideCharToMultiByte的使用請參考MSDN

          len = WideCharToMultiByte(CP_ACP,0,w_buffer,1024,desc,desc_len,NULL,NULL);

          //len = wcslen(w_buffer);

          if(len>0 && len 
          desc[len]=0;

          delete[] w_buffer;

          return strlen(desc);

          }







          · 方法二,將C的字符串轉為Java能識別的Unicode字符串 



          jstring NewJString(JNIEnv* env,LPCTSTR str)

          {

          if(!env || !str)

          return 0;

          int slen = strlen(str);

          jchar* buffer = new jchar[slen];

          int len = MultiByteToWideChar(CP_ACP,0,str,strlen(str),buffer,slen);

          if(len>0 && len < slen)

          buffer[len]=0;

          jstring js = env->NewString(buffer,len);

          delete [] buffer;

          return js;

          }



          異常





          由于調用了Java的方法,因此難免產(chǎn)生操作的異常信息。這些異常沒有辦法通過C++本身的
          異常處理機制來捕捉到,但JNI可以通過一些函數(shù)來獲取Java中拋出的異常信息。之前我們
          在Demo類中定義了一個方法throwExcp,下面將訪問該方法并捕捉其拋出來的異常信息,代
          碼如下: 



          /**

          假設我們已經(jīng)構造了一個Demo的實例obj,其類定義為cls

          */

          jthrowable excp = 0; /* 異常信息定義 */

          jmethodID mid=(*env)->GetMethodID(env,cls,"throwExcp","()V");

          /*如果mid為0表示獲取方法定義失敗*/

          jstring msg = (*env)-> CallVoidMethod(env, obj, mid);

          /* 在調用該方法后會有一個IllegalAccessException的異常拋出 */

          excp = (*env)->ExceptionOccurred(env);

          if(excp){ 

          (*env)->ExceptionClear(env);

          //通過訪問excp來獲取具體異常信息

          /*

          在Java中,大部分的異常信息都是擴展類java.lang.Exception,因此可以訪問excp的toS
          tring

          或者getMessage來獲取異常信息的內容。訪問這兩個方法同前面講到的如何訪問類的方法
          是相同的。

          */

          }


          線程和同步訪問

          有些時候需要使用多線程的方式來訪問Java的方法。我們知道一個Java虛擬機是非常消耗
          系統(tǒng)的內存資源,差不多每個虛擬機需要內存大約在20MB左右。為了節(jié)省資源要求每個線
          程使用的是同一個虛擬機,這樣在整個的JNI程序中只需要初始化一個虛擬機就可以了。所
          有人都是這樣想的,但是一旦子線程訪問主線程創(chuàng)建的虛擬機環(huán)境變量,系統(tǒng)就會出現(xiàn)錯
          誤對話框,然后整個程序終止。 



          其實這里面涉及到兩個概念,它們分別是虛擬機(JavaVM *jvm)和虛擬機環(huán)境(JNIEnv *e
          nv)。真正消耗大量系統(tǒng)資源的是jvm而不是env,jvm是允許多個線程訪問的,但是env只
          能被創(chuàng)建它本身的線程所訪問,而且每個線程必須創(chuàng)建自己的虛擬機環(huán)境env。這時候會有
          人提出疑問,主線程在初始化虛擬機的時候就創(chuàng)建了虛擬機環(huán)境env。為了讓子線程能夠創(chuàng)
          建自己的env,JNI提供了兩個函數(shù):AttachCurrentThread和DetachCurrentThread。下面
          代碼就是子線程訪問Java方法的框架: 



          DWORD WINAPI ThreadProc(PVOID dwParam)

          {

          JavaVM jvm = (JavaVM*)dwParam; /* 將虛擬機通過參數(shù)傳入 */

          JNIEnv* env;

          (*jvm)-> AttachCurrentThread(jvm, (void**)&env, NULL);

          .........

          (*jvm)-> DetachCurrentThread(jvm);

          }


          時間

          關于時間的話題是我在實際開發(fā)中遇到的一個問題。當要發(fā)布使用了JNI的程序時,并不一
          定要求客戶要安裝一個Java運行環(huán)境,因為可以在安裝程序中打包這個運行環(huán)境。為了讓
          打包程序利于下載,這個包要比較小,因此要去除JRE(Java運行環(huán)境)中一些不必要的文
          件。但是如果程序中用到Java中的日歷類型,例如java.util.Calendar等,那么有個文件
          一定不能去掉,這個文件就是[JRE]\lib\tzmappings。它是一個時區(qū)映射文件,一旦沒有
          該文件就會發(fā)現(xiàn)時間操作上經(jīng)常出現(xiàn)與正確時間相差幾個小時的情況。下面是打包JRE中必
          不可少的文件列表(以Windows環(huán)境為例),其中[JRE]為運行環(huán)境的目錄,同時這些文件之
          間的相對路徑不能變。 

          文件名 目錄 

          hpi.dll [JRE]\bin 

          ioser12.dll [JRE]\bin 

          java.dll [JRE]\bin 

          net.dll [JRE]\bin 

          verify.dll [JRE]\bin 

          zip.dll [JRE]\bin 

          jvm.dll [JRE]\bin\classic 

          rt.jar [JRE]\lib 

          tzmappings [JRE]\lib 

          由于rt.jar有差不多10MB,但是其中有很大一部分文件并不需要,可以根據(jù)實際的應用情
          況進行刪除。例如程序如果沒有用到Java Swing,就可以把涉及到Swing的文件都刪除后重
          新打包。 

          posted on 2005-11-06 14:26 春雷的博客 閱讀(479) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導航:
           
          主站蜘蛛池模板: 马公市| 尤溪县| 汉中市| 本溪市| 方正县| 江安县| 句容市| 韩城市| 阿尔山市| 巴东县| 罗江县| 舟曲县| 会泽县| 南投县| 商南县| 长顺县| 芦山县| 遂平县| 铁岭市| 高碑店市| 阿尔山市| 沙雅县| 隆子县| 高唐县| 丰城市| 南投市| 浠水县| 大城县| 宽甸| 西华县| 时尚| 徐州市| 通渭县| 巴彦淖尔市| 遂宁市| 尼玛县| 武川县| 涿州市| 讷河市| 泗洪县| 青田县|