|
作者:陳健明 作者簡(jiǎn)介陳健明,華中師范大學(xué)網(wǎng)絡(luò)與通訊研究所,您可以通過(guò)chenjm2000@hotmail.com和作者取得聯(lián)系。 內(nèi)容摘要JNI是JDK的一部分,用于為Java提供一個(gè)本地代碼的接口。通過(guò)使用JNI編寫(xiě)的程序能夠確保你的代碼能夠完全的移植到所有的平臺(tái)。JNI使得運(yùn)行在JVM虛擬機(jī)上的Java代碼能夠操作使用其它語(yǔ)言編寫(xiě)的應(yīng)用程序和庫(kù),比如C/C++以及匯編語(yǔ)言等。此外JNI提供的某些API還允許你把JVM嵌入到本地應(yīng)用程序中。下圖表達(dá)了JNI所扮演的角色。 本文將通過(guò)一個(gè)實(shí)例來(lái)闡述使用VC++6.0來(lái)實(shí)現(xiàn)JNI的完整過(guò)程。使用JNI來(lái)整合本地代碼和Java代碼的步驟是確定的,沒(méi)有再創(chuàng)作的余地,所以讀者可以通過(guò)本文的步驟來(lái)逐步認(rèn)識(shí)到,其實(shí)Java也是"沒(méi)有什么不可以"的。 一、JNI的實(shí)現(xiàn)任務(wù)描述:在Java中調(diào)用windows下的消息框函數(shù),并且從Java中傳遞一個(gè)字符串作為MessageBox函數(shù)的顯示文本參數(shù),顯示在消息框的中間。下面讓我們一起進(jìn)入這一奇妙的旅程。 Step 1:寫(xiě)一個(gè)Java類(lèi),在這個(gè)類(lèi)中包含了需要調(diào)用的本地方法的描述。
(1)中WinMsgDll是動(dòng)態(tài)鏈接文件的文件名,不用加擴(kuò)展名,因?yàn)樵诓煌钠脚_(tái)下動(dòng)態(tài)鏈接文件擴(kuò)展名是不同的,由JVM自動(dòng)識(shí)別,比如在Solaris下,會(huì)被轉(zhuǎn)換為WinMsgDll.so;而Win32環(huán)境下會(huì)轉(zhuǎn)換為WinMsgDll.dll。這個(gè)文件名必須和Step 4中生成的文件名一致。這個(gè)文件的存放位置也很重要,它只能被放在JVM屬性值java.library.path中指定的文件夾中。這個(gè)屬性值可以使用System.getProperty("java.library.path");來(lái)查看。一般情況下,至少放在這幾個(gè)位置是確定可靠的,windows安裝目錄下的system32下面,JDK安裝目錄下的bin下面,以及調(diào)用主類(lèi)文件的當(dāng)前目錄。 (2)中指明了你必須用本地代碼實(shí)現(xiàn)的方法。 Step 2:提示符下使用命令javac -d . WinMsgBox.java編譯Step 1編寫(xiě)的java文件。 此時(shí)會(huì)在當(dāng)前目錄下建立一個(gè)edu\netcom\jni目錄結(jié)構(gòu),并且一個(gè)WinMsgBox.class文件存在其中。 Step 3:提示符下使用命令javah -jni edu.netcom.jni.WinMsgBox,此時(shí)會(huì)在當(dāng)前目錄下產(chǎn)生一個(gè)edu_netcom_jni_WinMsgBox.h文件,注意這個(gè)文件名是由(包名+類(lèi)名)組成,中間用(_)隔開(kāi)。此文件內(nèi)容如下:
(1)包含的jni.h存在于JDK安裝目錄下的include下面。 (2)(Ljava/lang/String;)V這是函數(shù)的標(biāo)記符,當(dāng)從本地方法端訪(fǎng)問(wèn)Java端的方法時(shí),會(huì)用到這個(gè)標(biāo)記符。JNI中為每種數(shù)據(jù)類(lèi)型也定義了標(biāo)記符,標(biāo)記符的規(guī)則請(qǐng)查看JNI標(biāo)準(zhǔn)文檔。 (3)在WinMsgBox.java中本地方法void showMsgBox(String str);的定義,被映射為JNIEXPORT void JNICALL Java_edu_netcom_jni_WinMsgBox_showMsgBox(JNIEnv *, jobject, jstring); 其中函數(shù)名的映射規(guī)則是(Java_包名_類(lèi)名_方法名),如果存在重載的方法,則在后面還會(huì)增加每個(gè)參數(shù)的標(biāo)記符。每一個(gè)方法映射到本地C函數(shù)后都會(huì)增加兩個(gè)參數(shù):JNIEnv *和jobject,關(guān)于這兩個(gè)參數(shù)的用法將在后面闡述。另外,所有Java中的數(shù)據(jù)類(lèi)型都會(huì)按一定規(guī)則進(jìn)行映射為本地?cái)?shù)據(jù)類(lèi)型,這些數(shù)據(jù)類(lèi)型都是在jni.h中定義的。下面分別按照基本數(shù)據(jù)類(lèi)型,和對(duì)象類(lèi)型列出。 表1 Java基本類(lèi)型到本地類(lèi)型的映射 表2 Java中的類(lèi)到本地類(lèi)型的映射 Step 4:使用VC來(lái)編寫(xiě)本地方法的實(shí)現(xiàn)函數(shù),最后編譯成.dll文件。過(guò)程如下: 1) 選擇new->projects(選擇Win32 Dynamic-Link Library,以Step 1中指定的庫(kù)名WinMsgDll作為工程名)->OK->An ampty DLL project->Finish。 2) 選擇Tools->Options->Directories(添加目錄D:\J2SDK1.4.2_03\INCLUDE和D:\J2SDK1.4.2_03\INCLUDE\WIN32)。在這些目錄中包含JNI所需的頭文件。 3) 將Step 3生成的edu_netcom_jni_WinMsgBox.h拷貝到WinMsgDll工程文件夾中。然后FileView中添加這個(gè)頭文件。 4) 添加源文件WinMsgDll.cpp,內(nèi)容如下:
5) 編譯生成WinMsgBox.dll文件。并將這個(gè).dll文件拷貝到Step 1中說(shuō)明的目錄中。 注意: 1) 我們知道dll文件有兩種指明導(dǎo)出函數(shù)的方法,一種是在.def文件中定義,另一種是在定義函數(shù)時(shí)使用關(guān)鍵字__declspec(dllexport)。而在JNI中函數(shù)定義中的關(guān)鍵字JNIEXPORT實(shí)際在jni_md.h中如下定義,#define JNIEXPORT __declspec(dllexport),可見(jiàn)JNI默認(rèn)的導(dǎo)出函數(shù)使用第二種。使用第二種方式產(chǎn)生的導(dǎo)出函數(shù)名會(huì)根據(jù)編譯器發(fā)生變化,在有的情況下會(huì)發(fā)生找不到導(dǎo)出函數(shù)的問(wèn)題(我們?cè)贘SP中使用JNI時(shí)就發(fā)生了這種問(wèn)題,百思不得其解,后來(lái)強(qiáng)行加入一個(gè).def文件就解決了)。因此最好是使用第一種方法自己定義一個(gè).def文件來(lái)指明導(dǎo)出函數(shù),這種情況下會(huì)強(qiáng)制使用第一種方式產(chǎn)生導(dǎo)出函數(shù)。本例中可以加入一個(gè)WinMsgDll.def文件,內(nèi)容如下:
2) 從本例中,我們可以看到WinMsgBox.java決定了edu_netcom_jni_WinMsgBox.h,而后者又決定了WinMsgDll.dll,也就是說(shuō),這是一個(gè)"牽一發(fā)而動(dòng)全身"的過(guò)程,如果你改動(dòng)了WinMsgBox.java,就一定要把整個(gè)步驟都走一遍(這一點(diǎn)一定要切記,因?yàn)檫@也是我們跌得鼻青臉腫后才得出的警世良言)。 3) 生成的.dll文件一定要正確拷貝到Step 1說(shuō)明的目錄中,本例中是將生成的WinMsgDll.dll和Step 5中的測(cè)試文件放在同一個(gè)目錄下的(這也是我們困惑了很久才解決的問(wèn)題)。 Step 5:編寫(xiě)一個(gè)測(cè)試文件來(lái)測(cè)試對(duì)WinMsgDll.dll的調(diào)用。測(cè)試文件TestJNI.java內(nèi)容如下:
編譯,運(yùn)行,windows下的對(duì)話(huà)框躍然屏幕中間。到此為此,整個(gè)JNI的實(shí)現(xiàn)過(guò)程就已經(jīng)完成了。 二、補(bǔ)充說(shuō)明JNI為程序員提供了一種方法,使得他們能夠充分利用JVM以外的,完全由平臺(tái)決定的功能。但是你不應(yīng)該濫用JNI。大多數(shù)情況可能是因?yàn)檫@樣而必須使用JNI,如你已經(jīng)有做好了的本地.dll文件,其中已經(jīng)包含了大量嘔心瀝血的函數(shù),而你不愿意,也不可能完全用Java重寫(xiě)它們,那么此時(shí)你應(yīng)該用JNI。但是你必須按前面的步驟來(lái)進(jìn)行,只是在Step 4中編寫(xiě)的.dll要調(diào)用已有的本地.dll,也就是說(shuō)用Step 4中的.dll"封裝"已有的.dll。 在Java中定義的本地方法映射到本地C函數(shù)后都會(huì)增加兩個(gè)參數(shù):JNIEnv *和jobject。第一個(gè)參數(shù)JNIEnv是一個(gè)指針,指向在jni.h中定義的一個(gè)數(shù)據(jù)結(jié)構(gòu),結(jié)構(gòu)中包含了一系列的函數(shù)的指針,我們把它們稱(chēng)之為JNI函數(shù)。使用這些JNI函數(shù)可以使程序員從本地函數(shù)這一側(cè)完成對(duì)Java的操作,如訪(fǎng)問(wèn)Java字符串、數(shù)組、調(diào)用Java的方法、成員變量、甚至處理Java端的異常等。第二個(gè)參數(shù)會(huì)根據(jù)Java類(lèi)中本地方法的定義不同而不同,如果是定義為static方法,類(lèi)型會(huì)是jclass,表示對(duì)特定Class對(duì)象的引用,如果是非static方法,類(lèi)型是jobject,表示當(dāng)前對(duì)象的引用(本例中就是對(duì)WinMsgBox對(duì)象的引用),相當(dāng)于this。 C函數(shù)通過(guò)JNI接受來(lái)自Java的傳參也是按基本類(lèi)型直接傳值,對(duì)象傳引用。對(duì)于基本類(lèi)型可以按照表1來(lái)使用,其它類(lèi)型都必須使用JNI函數(shù)進(jìn)行轉(zhuǎn)換后才能在C函數(shù)中使用。如本例中從Java中傳遞了一個(gè)字符串,映射為JNI類(lèi)型jstring。使用JNI函數(shù)GetStringUTFChars將jstring轉(zhuǎn)換為UTF-8字符串,然后便可以使用C語(yǔ)言中的任何字符串操作函數(shù)進(jìn)行操作。由于JVM在調(diào)用本地方法時(shí),是在虛擬機(jī)中開(kāi)辟了一塊本地方法棧供本地方法使用,當(dāng)本地方法使用完UTF-8串后,必須使用ReleaseStringUTFChars,通過(guò)它來(lái)通知虛擬機(jī)去回收UTF-8串占用的內(nèi)存,否則將會(huì)造成內(nèi)存泄漏,最終導(dǎo)致系統(tǒng)崩潰。同樣的,如果需要將C函數(shù)返回的返回值能夠正確通過(guò)JNI傳給Java,也要使用JNI函數(shù)轉(zhuǎn)換為前面兩個(gè)表中的類(lèi)型。 關(guān)于 JNI 函數(shù)的權(quán)威文檔請(qǐng)參閱 Java Native Interface 1.5 Specification 關(guān)于 JNI 函數(shù)的教程請(qǐng)參閱 Java Native Interface Tutorial |
|
只有注冊(cè)用戶(hù)登錄后才能發(fā)表評(píng)論。 | ||
![]() |
||
網(wǎng)站導(dǎo)航:
博客園
IT新聞
Chat2DB
C++博客
博問(wèn)
管理
|
||
相關(guān)文章:
|
||