張美玲,張 剛
(太原理工大學(xué) 信息工程學(xué)院,山西 太原 030024)
Java是一種面向?qū)ο蟮木幊陶Z(yǔ)言,它雖然具有語(yǔ)法簡(jiǎn)單,面向?qū)ο?,穩(wěn)定,可移植性高等優(yōu)點(diǎn),但Java[1]以其跨平臺(tái)的目的,使得它不能像其它語(yǔ)言(如C和匯編)那樣更接近操作系統(tǒng),和本地機(jī)器的各種內(nèi)部聯(lián)系變得很少,也就不能和操作系統(tǒng)的底層打交道了。為了解決Java與底層的交互,本文引入JNI技術(shù),通過(guò)Java對(duì)本地方法的調(diào)用,實(shí)現(xiàn)應(yīng)用層對(duì)磁盤(pán)的直接操作。JNI的提出主要有以下幾個(gè)原因[2-3]:第一,當(dāng)標(biāo)準(zhǔn)的java的類庫(kù)不支持程序所需特性時(shí),可以用其它語(yǔ)言實(shí)現(xiàn)的接口。第二,需要用底層語(yǔ)言實(shí)現(xiàn)一個(gè)小型的時(shí)間敏感代碼時(shí),考慮到j(luò)ava運(yùn)行速度要比C/C++慢,需要引入JNI。第三,已經(jīng)有了一個(gè)用其他語(yǔ)言寫(xiě)成的庫(kù)或程序時(shí),可以用java直接來(lái)調(diào)用,減少工作量。本文是基于java的類庫(kù)不支持程序所需特性的原因,用C++編寫(xiě)讀寫(xiě)扇區(qū)本地代碼并生成DLL[4](WINDOWS平臺(tái)下是.DLL 文件,Linux平臺(tái)下是.SO 文件)文件。雖然在DOS環(huán)境下,通過(guò)中斷或IO[5]可以直接對(duì)硬盤(pán)進(jìn)行操縱,因?yàn)锽IOS和DOS系統(tǒng)為磁盤(pán)操作提供了INT13H 中斷,通過(guò)INT13H的讀寫(xiě)功能可以實(shí)現(xiàn)磁盤(pán)的直接讀寫(xiě)。但在Windows環(huán)境下,Win32系統(tǒng)禁止應(yīng)用程序?qū)τ脖P(pán)直接操縱,禁止使用BIOS 中斷。所以,Windows操作系統(tǒng)下,在應(yīng)用層直接讀取硬盤(pán)扇區(qū)變的困難。但并不意味著在windows環(huán)境下無(wú)法訪問(wèn)硬盤(pán)。它在采取了“實(shí)保護(hù)”的同時(shí),也提供了一些API函數(shù),本文通過(guò)ReadFile()和WriteFile()函數(shù),以扇區(qū)存取的方式在本地代碼中實(shí)現(xiàn)讀寫(xiě)扇區(qū),最后通過(guò)JNI方法實(shí)現(xiàn)了java對(duì)磁盤(pán)扇區(qū)的讀寫(xiě)操作。
目前java與dll交互的技術(shù)主要有3種:jni,jawin和jacob。JNI是sun公司提供的java與系統(tǒng)中的原生方法交互的技術(shù)(在windows\linux 系統(tǒng)中,本文是基于windows平臺(tái)),它是JDK的一部分,提供了java與本地非java語(yǔ)言代碼的接口,通過(guò)使用JNI編寫(xiě)的程序才能夠確保代碼移植到所有的平臺(tái)。該平臺(tái)相關(guān)代碼是通過(guò)JNI函數(shù)來(lái)訪問(wèn)Java虛擬機(jī)功能的,而JNI函數(shù)需要通過(guò)第一個(gè)接口指針JINEnv*[6]獲取。接口指針是指針的指針,它先指向一個(gè)JNI函數(shù)指針數(shù)組,而指針數(shù)組中每個(gè)元素又指向JNI接口函數(shù)。需要注意的是,本地方法將JNI接口指針當(dāng)做參數(shù)來(lái)傳遞,所以在一個(gè)線程中對(duì)本地方法的多次調(diào)用,需要保證接口指針是相同的。但是,如果一個(gè)方法被不同的線程調(diào)用,需要不同的JNI接口指針。以下是JNI原理圖,如圖1所示。
圖1 JNI原理
JNI實(shí)現(xiàn)的最終目標(biāo)是要通過(guò)編寫(xiě)頭文件及本地程序,結(jié)合C/C++文件生成動(dòng)態(tài)庫(kù)文件,最后加載到j(luò)ava程序運(yùn)行成功。具體實(shí)現(xiàn)步驟[7]如圖2所示。
圖2 JNI實(shí)現(xiàn)步驟
下面結(jié)合實(shí)例具體分析JNI調(diào)用過(guò)程。
該實(shí)例結(jié)合結(jié)合java對(duì)dll文件調(diào)用方法,在VC++中編寫(xiě)本地代碼,并編譯生成.dll文件,通過(guò)并java 對(duì).dll文件調(diào)用,實(shí)現(xiàn)了java對(duì)磁盤(pán)扇區(qū)的直接讀寫(xiě)。以下是VC++中實(shí)現(xiàn)讀寫(xiě)扇區(qū)基本原理以及java實(shí)現(xiàn)讀寫(xiě)扇區(qū)的基本步驟。
首先,使用CreateFile函數(shù)打開(kāi)磁盤(pán)驅(qū)動(dòng),指定所要操作磁盤(pán)并設(shè)置讀或?qū)懖僮鳎摵瘮?shù)參數(shù)設(shè)置如下:
打開(kāi)的文件名參數(shù)設(shè)置:對(duì)于讀寫(xiě)扇區(qū),如果訪問(wèn)的是具體某個(gè)邏輯分區(qū),則文件名格式為“\\.\X”,如果訪問(wèn)的是第一個(gè)邏輯硬盤(pán),則文件名格式為“\\.\PHYSICALDRIVE0”;文件的操作屬性設(shè)置:允許讀設(shè)備操作設(shè)置為GENERIC_READ,允許寫(xiě)設(shè)備操作設(shè)置為GENERIC_WRITE;文件共享屬性設(shè)置:FILE_SHARE_READ和FILE_SHARE_WRITE分別表示允許對(duì)設(shè)備進(jìn)行讀共享訪問(wèn)和寫(xiě)共享訪問(wèn);文件操作設(shè)置為OPEN_EXISTING,對(duì)于該設(shè)置文件必須已經(jīng)存在,由設(shè)備提出要求,若該文件不存在,則函數(shù)調(diào)用失敗。
其次,因?yàn)樗x的是磁盤(pán)中某個(gè)扇區(qū),而打開(kāi)的是整個(gè)磁盤(pán),所以要通過(guò)SetFilePointer函數(shù)設(shè)置文件指針到磁盤(pán)中所要操作的某個(gè)扇區(qū)位置,CreateFile函數(shù)參數(shù)設(shè)置如下:
其中文件句柄是CreateFile函數(shù)所返回的句柄,如果該句柄值表示磁盤(pán)打開(kāi)成功,則通過(guò)設(shè)置字節(jié)偏移量將指針指定到所要操作扇區(qū),對(duì)于讀寫(xiě)扇區(qū)操作,將字節(jié)偏移量設(shè)置為指針移動(dòng)的字節(jié)數(shù);文件定位設(shè)置為FILE_BEGIN,即從文件開(kāi)始為參考位置進(jìn)行讀寫(xiě)。
接著,就可以利用ReadFile和WriteFile從指定位置讀寫(xiě)扇區(qū),該函數(shù)由五個(gè)參數(shù)組成,參數(shù)設(shè)置如下:
第一個(gè)參數(shù)為文件句柄,同上。第二個(gè)參數(shù)為緩沖區(qū),表示用于保存讀/寫(xiě)入數(shù)據(jù)的一個(gè)緩沖區(qū)。第三個(gè)參數(shù)為要讀或?qū)懭氲淖址麛?shù),此處設(shè)置為從文件中讀或?qū)懭氲臄?shù)據(jù)字節(jié)數(shù)。第四個(gè)參數(shù)為從文件中實(shí)際讀或?qū)懭氲淖止?jié)數(shù)的指針。第五個(gè)參數(shù)設(shè)為NULL。
最后,完成訪問(wèn)操作后,如果讀或?qū)懮葏^(qū)失敗,顯示錯(cuò)誤信息;如果讀或?qū)懮葏^(qū)成功,則用CloseHandle()函數(shù)關(guān)閉文件句柄,從而完成一次完整的磁盤(pán)扇區(qū)讀寫(xiě)操作訪問(wèn),具體操作流程如圖3所示。
圖3 流程
以上是在VC++環(huán)境下實(shí)現(xiàn)讀寫(xiě)扇區(qū)的方法,而要想在應(yīng)用層實(shí)現(xiàn)底層磁盤(pán)的操作,需要通過(guò)java來(lái)對(duì)本地方法進(jìn)行調(diào)用,下面以磁盤(pán)數(shù)據(jù)讀寫(xiě)為例,先在磁盤(pán)中寫(xiě)入數(shù)據(jù),再通過(guò)讀扇區(qū)的方式讀取磁盤(pán)信息,分析了dll文件的生成以及java對(duì)其的調(diào)用過(guò)程,具體流程如下[9-10]
(1)建立Java工程writesector和readsector,分別在Writesector.java和Readsector.java中聲明本地方法。
public native boolean writeSector(long StartSector,int data);
public native boolean readSector(long StartSector);
定義了方法writeSector和readSector,參數(shù)StartSector,類型為long,表示所讀或?qū)懮葏^(qū)號(hào),這里是邏輯扇區(qū)號(hào),此參數(shù)用來(lái)在設(shè)置文件指針位置時(shí)指定到所要讀或?qū)懙纳葏^(qū)。data表示寫(xiě)入扇區(qū)中數(shù)據(jù)。返回參數(shù)類型均為布爾類型。由于Java和C的編碼方式不同,所以JNI技術(shù)最關(guān)鍵部分就是參數(shù)的傳遞,即將本地代碼中的參數(shù)轉(zhuǎn)換為java可調(diào)用的參數(shù)類型,JNI數(shù)據(jù)類型映射見(jiàn)表1。
表1 JNI數(shù)據(jù)類型映射
native關(guān)鍵字作用:聲明本地化方法。它告訴Java 編譯器,方法是用Java類之外的本機(jī)代碼實(shí)現(xiàn)的,不需要用Java代碼具體實(shí)現(xiàn),但其聲明卻在Java中。
(2)加載動(dòng)態(tài)庫(kù)
Writesector.java和Readsector.java中分別加載write-sector1和readsector1文件。Load關(guān)鍵字:聲明的本地方法沒(méi)有實(shí)現(xiàn),但是我們?cè)谙旅婢椭苯邮褂昧?,所以必須在使用之前?duì)它進(jìn)行初始化。這里一般是以static塊進(jìn)行加載的。其中“writesector1”和“readsector1”是動(dòng)態(tài)庫(kù)的名字,Java通過(guò)調(diào)用這個(gè)中介Dll中的writeSector和readSector方法,間接調(diào)用真正的第三方Dll。
(3)編譯生成Writesector.class和Readsector.class文件。
(4)運(yùn)用.class文件生成.h頭文件。
(5)用VC6.0編寫(xiě)生成dll文件。下面以讀文件為例分析生成dll文件過(guò)程。
第1步:在VC++下新建一個(gè)Win32Dynamic-Link Library類型的工程,取名readsector1,其中readsector1就是將來(lái)要生成的dll文件名,這樣命名方便java 對(duì)其直接調(diào)用。
第2步:將頭文件readsector_Readsector.h、jni.h和jni_md.h 添加到工程中去,其中jni.h和jni_md.h這兩個(gè)文件可以在jdk1.6的include目錄下找到。
第3步:編寫(xiě)readsector.cpp實(shí)現(xiàn)readSector函數(shù)。
JNIEXPORT jboolean JNICALL Java_readsector_Readsector_readSector(JNIEnv*env,jobject obj,jlong StartSector)
其中JNIEnv* 是一個(gè)指向函數(shù)指針表的指針,這些函數(shù)提供各種用來(lái)在C++中操作Java數(shù)據(jù)的能力。jobject是指向在此Java代碼中實(shí)例化的Java 對(duì)象的一個(gè)句柄。jlong和jboolean分別對(duì)應(yīng)Java 中輸入函數(shù)類型和輸出類型。
JNIEXPORT和JNICALL 都是JNI的關(guān)鍵字JNIEXPORT 表示函數(shù)的鏈接方式,當(dāng)程序執(zhí)行時(shí)從本地庫(kù)文件中找函數(shù),JNICALL 表示調(diào)用約定,說(shuō)明調(diào)用的是本地方法。
以下是readSector函數(shù)中的主要部分:
第4步:使用VC++編譯器編譯.cpp,生成readsector1.dll文件。
(1)在Writesector.java中輸入測(cè)試代碼:
Writesector sample=new Writesector();
boolean bool=sample.writeSector(2149033,0xBB);
System.out.println("writeSector:"+bool);
為了方便測(cè)試,在邏輯扇區(qū)號(hào)為2149033的扇區(qū)中輸入同一個(gè)數(shù)值0xbb。
在Readsector.java中輸入測(cè)試代碼:
Readsector sample=new Readsector();
boolean bool=sample.readSector(2149033);
System.out.println("readSector:"+bool);
為了檢驗(yàn)寫(xiě)扇區(qū)的正確性,檢驗(yàn)邏輯扇區(qū)號(hào)為2149033的扇區(qū)值是否正確。
(2)將readsector1.dll拷貝到Readsector.java所在的目錄下,將writesector1.dll拷貝到Writesector.java 所在的目錄下。
(3)運(yùn)行Writesector.java,實(shí)現(xiàn)寫(xiě)扇區(qū),輸出結(jié)果如圖4所示。
圖4 讀扇區(qū)
再運(yùn)行Readsector.java,讀取所寫(xiě)入扇區(qū)值。輸出結(jié)果如圖5所示。
圖5 寫(xiě)扇區(qū)
Java所調(diào)用的本地方法是指包含在特定平臺(tái)下的可執(zhí)行文件中,就本文示例而言,本地方法即包含在windows平臺(tái)下的動(dòng)態(tài)鏈接庫(kù)DLL中。在java對(duì)本地方法的實(shí)際調(diào)用過(guò)程中,需考慮一下兩個(gè)準(zhǔn)則:
(1)當(dāng)本地代碼有多個(gè)方法時(shí),可以將這些本地方法都封裝到單個(gè)類中,這個(gè)類只需要調(diào)用一個(gè)DLL,即可實(shí)現(xiàn)對(duì)本地代碼的調(diào)用。對(duì)于每種目標(biāo)操作系統(tǒng),只需要修改基于該平臺(tái)的本地代碼來(lái)替換DLL,這就將本地代碼的影響減小對(duì)最小,也有助于不同平臺(tái)下的一直問(wèn)題。
(2)本地方法要簡(jiǎn)單。目的要使第三方運(yùn)行時(shí)對(duì)DLL依賴程度減到最小,從而使本地方法更加獨(dú)立,減小加載DLL和應(yīng)用程序的開(kāi)銷。
(1)Java作為一種面向?qū)ο蟮木幊陶Z(yǔ)言,雖然具有跨平臺(tái)等優(yōu)點(diǎn),但JNI方法在實(shí)現(xiàn)java與本地代碼交互的同時(shí)也限制了java語(yǔ)言的一個(gè)優(yōu)點(diǎn):程序的可移植性。java調(diào)用本地方法時(shí),需要本地代碼為其提供動(dòng)態(tài)鏈接庫(kù),而鏈接庫(kù)本身是與平臺(tái)相關(guān)的。
(2)JNI方法使程序的安全性降低。JVM 給Java代碼提供了完善的安全機(jī)制使得Java代碼不會(huì)導(dǎo)致程序崩潰、濫用數(shù)據(jù)等,一旦使用了JNI,這種安全機(jī)制就無(wú)能力了。
(3)必須確保本地代碼的穩(wěn)定性,因?yàn)楸镜卮a運(yùn)行時(shí)可能會(huì)造成錯(cuò)誤指針帶來(lái)的間接錯(cuò)誤,這樣本地代碼帶來(lái)的絲毫錯(cuò)誤都可能導(dǎo)致java虛擬機(jī)的崩潰[11]。
本文分析了基于windows平臺(tái)下用VC++實(shí)現(xiàn)磁盤(pán)扇區(qū)的讀寫(xiě)方法之后,通過(guò)JNI技術(shù)實(shí)現(xiàn)了java對(duì)VC++下生成的dll文件的調(diào)用,從而完成應(yīng)用層對(duì)磁盤(pán)的直接訪問(wèn),實(shí)現(xiàn)了java對(duì)系統(tǒng)底層的直接操作。由于java標(biāo)準(zhǔn)的類庫(kù)無(wú)法支持與硬件的交互,這就受限了JNI方法的使用。而JNI方法在
實(shí)現(xiàn)java與本地代碼雙向交互的同時(shí),使得程序本身喪失了跨平臺(tái)的優(yōu)點(diǎn)。所以,在使用JNI方法之前,一定要審查是否有更好的方法結(jié)合到j(luò)ava中。本文是在windows平臺(tái)實(shí)現(xiàn)了對(duì)系統(tǒng)底層的一些操作,如果想要跨平臺(tái)實(shí)現(xiàn),這就要求在不同的操作系統(tǒng)下重新編譯本地代碼,通過(guò)使用JNI技術(shù)可以實(shí)現(xiàn)更為廣泛的應(yīng)用層與底層之間的交互,有待進(jìn)一步研究。
[1]Eckel.Thinking in java 4[M].Beijing:Publishing House of Electronics Industry,2011(in Chinese).[??藸?java編程思想第四版[M].北京:電子工業(yè)出版社,2011.]
[2]WANG Jundi,ZHAO Kai.Study of JNI technology applied in software development[J].Journal of Lanzhou Polytechnic College,2009,16(5):15-17(in Chinese).[王軍弟,趙愷.JNI技術(shù)在軟件開(kāi)發(fā)中的應(yīng)用研究[J].蘭州工業(yè)高等專科學(xué)校學(xué)報(bào),2009,16(5):15-17.]
[3]GAO Jing,WANG Jianhua.The application of jni technique in the built-in software development[J].Natural Science Journal of Harbin Normal University,2007,23(6):62-65(in Chinese).[高晶,王建華.JNI技術(shù)在嵌入式軟件開(kāi)發(fā)中的應(yīng)用[J].哈爾濱師范大學(xué)自然科學(xué)學(xué)報(bào),2007,23(6):62-65.]
[4]MA Liyan,ZHANG Chunfang,LI Ruitai,et al.Empoldering database DLL program in the environment of delphi[J].Journal of Hebei Normal University(Natural Science Edition,2007,31(2):173-175(in Chinese).[馬麗艷,張春芳,李瑞臺(tái),等.用Delphi開(kāi)發(fā)數(shù)據(jù)庫(kù)應(yīng)用功能的DLL 程序[J].河北師范大學(xué)學(xué)報(bào)(自然科學(xué)版),2007,31(2):173-175.]
[5]CHEN Jie,ZHANG Wei,ZHANG Shunsheng.Design and implementation of SATA2.0controller[J].Journal of Computer Applications,2011,31(S1):25-26(in Chinese).[陳杰,張偉,張順生.SATA2.0 控制器的設(shè)計(jì)與實(shí)現(xiàn)[J].計(jì)算機(jī)應(yīng)用,2011,31(S1):25-26.]
[6]LIU Yingming,LI Ning,ZHANG Ling,et al.Using JNI to establish communication between Java and C++[J].Computer Era,2010,31(6):980-984(in Chinese).[劉英明,李寧,張玲,等.基于JNI技術(shù)C++測(cè)井應(yīng)用程序集成方法[J].石油學(xué)報(bào),2010,31(6):980-984.]
[7]AN Baijun,GAO Dong,ZHANG Wei,et al.Java native method calls[J].Microprocessors,2011,2(2):40-44(in Chi-nese).[安百俊,高棟,張偉,等.通過(guò)Java 調(diào)用本地方法[J].微處理機(jī),2011,2(2):40-44.]
[8]WANG Hong.Read floppy disk sector for Windows[J].Computer Knowledge and Technology,2009,5(24):6791-6793(in Chinese).[汪虹.Windows下直接讀取軟盤(pán)扇區(qū)[J].電腦知識(shí)與技術(shù),2009,5(24):6791-6793.]
[9]GUO Liquan,XIE Weibo.Design and realization of video intercom system based on Andriod[J].Microcomputer &Its Applications,2012,31(5):4-7(in Chinese).[郭利全,謝維波.基于Android平臺(tái)的可視對(duì)講系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)[J].微型機(jī)與應(yīng)用,2012,31(5):4-7.]
[10]ZHANG Miaomiao,XING Jianchun,YANG Qihang.Method and implementation of call on configuration software database based on jni technology[J].Industrial Control Computer,2011,24(9):3-5(in Chinese).[張淼淼,邢建春,楊啟亮.基于JNI技術(shù)的組態(tài)軟件數(shù)據(jù)庫(kù)訪問(wèn)方法及應(yīng)用[J].工業(yè)控制計(jì)算機(jī),2011,24(9):3-5.]
[11]HUANG Yanfeng,WANG Jianpin.Comparisons between Java and C++programming language on security[J].Sichuan University of Arts and Science Journal(Natural Science Edition),2007,17(2):53-54(in Chinese).[黃艷峰,王建品.Java與C++在安全性方面的比較[J].四川文理學(xué)院學(xué)報(bào)(自然科學(xué)版),2007,17(2):53-54.]