張美玲,張 剛
(太原理工大學 信息工程學院,山西 太原 030024)
Java是一種面向對象的編程語言,它雖然具有語法簡單,面向對象,穩(wěn)定,可移植性高等優(yōu)點,但Java[1]以其跨平臺的目的,使得它不能像其它語言(如C和匯編)那樣更接近操作系統(tǒng),和本地機器的各種內部聯(lián)系變得很少,也就不能和操作系統(tǒng)的底層打交道了。為了解決Java與底層的交互,本文引入JNI技術,通過Java對本地方法的調用,實現應用層對磁盤的直接操作。JNI的提出主要有以下幾個原因[2-3]:第一,當標準的java的類庫不支持程序所需特性時,可以用其它語言實現的接口。第二,需要用底層語言實現一個小型的時間敏感代碼時,考慮到java運行速度要比C/C++慢,需要引入JNI。第三,已經有了一個用其他語言寫成的庫或程序時,可以用java直接來調用,減少工作量。本文是基于java的類庫不支持程序所需特性的原因,用C++編寫讀寫扇區(qū)本地代碼并生成DLL[4](WINDOWS平臺下是.DLL 文件,Linux平臺下是.SO 文件)文件。雖然在DOS環(huán)境下,通過中斷或IO[5]可以直接對硬盤進行操縱,因為BIOS和DOS系統(tǒng)為磁盤操作提供了INT13H 中斷,通過INT13H的讀寫功能可以實現磁盤的直接讀寫。但在Windows環(huán)境下,Win32系統(tǒng)禁止應用程序對硬盤直接操縱,禁止使用BIOS 中斷。所以,Windows操作系統(tǒng)下,在應用層直接讀取硬盤扇區(qū)變的困難。但并不意味著在windows環(huán)境下無法訪問硬盤。它在采取了“實保護”的同時,也提供了一些API函數,本文通過ReadFile()和WriteFile()函數,以扇區(qū)存取的方式在本地代碼中實現讀寫扇區(qū),最后通過JNI方法實現了java對磁盤扇區(qū)的讀寫操作。
目前java與dll交互的技術主要有3種:jni,jawin和jacob。JNI是sun公司提供的java與系統(tǒng)中的原生方法交互的技術(在windows\linux 系統(tǒng)中,本文是基于windows平臺),它是JDK的一部分,提供了java與本地非java語言代碼的接口,通過使用JNI編寫的程序才能夠確保代碼移植到所有的平臺。該平臺相關代碼是通過JNI函數來訪問Java虛擬機功能的,而JNI函數需要通過第一個接口指針JINEnv*[6]獲取。接口指針是指針的指針,它先指向一個JNI函數指針數組,而指針數組中每個元素又指向JNI接口函數。需要注意的是,本地方法將JNI接口指針當做參數來傳遞,所以在一個線程中對本地方法的多次調用,需要保證接口指針是相同的。但是,如果一個方法被不同的線程調用,需要不同的JNI接口指針。以下是JNI原理圖,如圖1所示。
圖1 JNI原理
JNI實現的最終目標是要通過編寫頭文件及本地程序,結合C/C++文件生成動態(tài)庫文件,最后加載到java程序運行成功。具體實現步驟[7]如圖2所示。
圖2 JNI實現步驟
下面結合實例具體分析JNI調用過程。
該實例結合結合java對dll文件調用方法,在VC++中編寫本地代碼,并編譯生成.dll文件,通過并java 對.dll文件調用,實現了java對磁盤扇區(qū)的直接讀寫。以下是VC++中實現讀寫扇區(qū)基本原理以及java實現讀寫扇區(qū)的基本步驟。
首先,使用CreateFile函數打開磁盤驅動,指定所要操作磁盤并設置讀或寫操作,該函數參數設置如下:
打開的文件名參數設置:對于讀寫扇區(qū),如果訪問的是具體某個邏輯分區(qū),則文件名格式為“\\.\X”,如果訪問的是第一個邏輯硬盤,則文件名格式為“\\.\PHYSICALDRIVE0”;文件的操作屬性設置:允許讀設備操作設置為GENERIC_READ,允許寫設備操作設置為GENERIC_WRITE;文件共享屬性設置:FILE_SHARE_READ和FILE_SHARE_WRITE分別表示允許對設備進行讀共享訪問和寫共享訪問;文件操作設置為OPEN_EXISTING,對于該設置文件必須已經存在,由設備提出要求,若該文件不存在,則函數調用失敗。
其次,因為所要讀的是磁盤中某個扇區(qū),而打開的是整個磁盤,所以要通過SetFilePointer函數設置文件指針到磁盤中所要操作的某個扇區(qū)位置,CreateFile函數參數設置如下:
其中文件句柄是CreateFile函數所返回的句柄,如果該句柄值表示磁盤打開成功,則通過設置字節(jié)偏移量將指針指定到所要操作扇區(qū),對于讀寫扇區(qū)操作,將字節(jié)偏移量設置為指針移動的字節(jié)數;文件定位設置為FILE_BEGIN,即從文件開始為參考位置進行讀寫。
接著,就可以利用ReadFile和WriteFile從指定位置讀寫扇區(qū),該函數由五個參數組成,參數設置如下:
第一個參數為文件句柄,同上。第二個參數為緩沖區(qū),表示用于保存讀/寫入數據的一個緩沖區(qū)。第三個參數為要讀或寫入的字符數,此處設置為從文件中讀或寫入的數據字節(jié)數。第四個參數為從文件中實際讀或寫入的字節(jié)數的指針。第五個參數設為NULL。
最后,完成訪問操作后,如果讀或寫扇區(qū)失敗,顯示錯誤信息;如果讀或寫扇區(qū)成功,則用CloseHandle()函數關閉文件句柄,從而完成一次完整的磁盤扇區(qū)讀寫操作訪問,具體操作流程如圖3所示。
圖3 流程
以上是在VC++環(huán)境下實現讀寫扇區(qū)的方法,而要想在應用層實現底層磁盤的操作,需要通過java來對本地方法進行調用,下面以磁盤數據讀寫為例,先在磁盤中寫入數據,再通過讀扇區(qū)的方式讀取磁盤信息,分析了dll文件的生成以及java對其的調用過程,具體流程如下[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,參數StartSector,類型為long,表示所讀或寫扇區(qū)號,這里是邏輯扇區(qū)號,此參數用來在設置文件指針位置時指定到所要讀或寫的扇區(qū)。data表示寫入扇區(qū)中數據。返回參數類型均為布爾類型。由于Java和C的編碼方式不同,所以JNI技術最關鍵部分就是參數的傳遞,即將本地代碼中的參數轉換為java可調用的參數類型,JNI數據類型映射見表1。
表1 JNI數據類型映射
native關鍵字作用:聲明本地化方法。它告訴Java 編譯器,方法是用Java類之外的本機代碼實現的,不需要用Java代碼具體實現,但其聲明卻在Java中。
(2)加載動態(tài)庫
Writesector.java和Readsector.java中分別加載write-sector1和readsector1文件。Load關鍵字:聲明的本地方法沒有實現,但是我們在下面就直接使用了,所以必須在使用之前對它進行初始化。這里一般是以static塊進行加載的。其中“writesector1”和“readsector1”是動態(tài)庫的名字,Java通過調用這個中介Dll中的writeSector和readSector方法,間接調用真正的第三方Dll。
(3)編譯生成Writesector.class和Readsector.class文件。
(4)運用.class文件生成.h頭文件。
(5)用VC6.0編寫生成dll文件。下面以讀文件為例分析生成dll文件過程。
第1步:在VC++下新建一個Win32Dynamic-Link Library類型的工程,取名readsector1,其中readsector1就是將來要生成的dll文件名,這樣命名方便java 對其直接調用。
第2步:將頭文件readsector_Readsector.h、jni.h和jni_md.h 添加到工程中去,其中jni.h和jni_md.h這兩個文件可以在jdk1.6的include目錄下找到。
第3步:編寫readsector.cpp實現readSector函數。
JNIEXPORT jboolean JNICALL Java_readsector_Readsector_readSector(JNIEnv*env,jobject obj,jlong StartSector)
其中JNIEnv* 是一個指向函數指針表的指針,這些函數提供各種用來在C++中操作Java數據的能力。jobject是指向在此Java代碼中實例化的Java 對象的一個句柄。jlong和jboolean分別對應Java 中輸入函數類型和輸出類型。
JNIEXPORT和JNICALL 都是JNI的關鍵字JNIEXPORT 表示函數的鏈接方式,當程序執(zhí)行時從本地庫文件中找函數,JNICALL 表示調用約定,說明調用的是本地方法。
以下是readSector函數中的主要部分:
第4步:使用VC++編譯器編譯.cpp,生成readsector1.dll文件。
(1)在Writesector.java中輸入測試代碼:
Writesector sample=new Writesector();
boolean bool=sample.writeSector(2149033,0xBB);
System.out.println("writeSector:"+bool);
為了方便測試,在邏輯扇區(qū)號為2149033的扇區(qū)中輸入同一個數值0xbb。
在Readsector.java中輸入測試代碼:
Readsector sample=new Readsector();
boolean bool=sample.readSector(2149033);
System.out.println("readSector:"+bool);
為了檢驗寫扇區(qū)的正確性,檢驗邏輯扇區(qū)號為2149033的扇區(qū)值是否正確。
(2)將readsector1.dll拷貝到Readsector.java所在的目錄下,將writesector1.dll拷貝到Writesector.java 所在的目錄下。
(3)運行Writesector.java,實現寫扇區(qū),輸出結果如圖4所示。
圖4 讀扇區(qū)
再運行Readsector.java,讀取所寫入扇區(qū)值。輸出結果如圖5所示。
圖5 寫扇區(qū)
Java所調用的本地方法是指包含在特定平臺下的可執(zhí)行文件中,就本文示例而言,本地方法即包含在windows平臺下的動態(tài)鏈接庫DLL中。在java對本地方法的實際調用過程中,需考慮一下兩個準則:
(1)當本地代碼有多個方法時,可以將這些本地方法都封裝到單個類中,這個類只需要調用一個DLL,即可實現對本地代碼的調用。對于每種目標操作系統(tǒng),只需要修改基于該平臺的本地代碼來替換DLL,這就將本地代碼的影響減小對最小,也有助于不同平臺下的一直問題。
(2)本地方法要簡單。目的要使第三方運行時對DLL依賴程度減到最小,從而使本地方法更加獨立,減小加載DLL和應用程序的開銷。
(1)Java作為一種面向對象的編程語言,雖然具有跨平臺等優(yōu)點,但JNI方法在實現java與本地代碼交互的同時也限制了java語言的一個優(yōu)點:程序的可移植性。java調用本地方法時,需要本地代碼為其提供動態(tài)鏈接庫,而鏈接庫本身是與平臺相關的。
(2)JNI方法使程序的安全性降低。JVM 給Java代碼提供了完善的安全機制使得Java代碼不會導致程序崩潰、濫用數據等,一旦使用了JNI,這種安全機制就無能力了。
(3)必須確保本地代碼的穩(wěn)定性,因為本地代碼運行時可能會造成錯誤指針帶來的間接錯誤,這樣本地代碼帶來的絲毫錯誤都可能導致java虛擬機的崩潰[11]。
本文分析了基于windows平臺下用VC++實現磁盤扇區(qū)的讀寫方法之后,通過JNI技術實現了java對VC++下生成的dll文件的調用,從而完成應用層對磁盤的直接訪問,實現了java對系統(tǒng)底層的直接操作。由于java標準的類庫無法支持與硬件的交互,這就受限了JNI方法的使用。而JNI方法在
實現java與本地代碼雙向交互的同時,使得程序本身喪失了跨平臺的優(yōu)點。所以,在使用JNI方法之前,一定要審查是否有更好的方法結合到java中。本文是在windows平臺實現了對系統(tǒng)底層的一些操作,如果想要跨平臺實現,這就要求在不同的操作系統(tǒng)下重新編譯本地代碼,通過使用JNI技術可以實現更為廣泛的應用層與底層之間的交互,有待進一步研究。
[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技術在軟件開發(fā)中的應用研究[J].蘭州工業(yè)高等??茖W校學報,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技術在嵌入式軟件開發(fā)中的應用[J].哈爾濱師范大學自然科學學報,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).[馬麗艷,張春芳,李瑞臺,等.用Delphi開發(fā)數據庫應用功能的DLL 程序[J].河北師范大學學報(自然科學版),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 控制器的設計與實現[J].計算機應用,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技術C++測井應用程序集成方法[J].石油學報,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).[安百俊,高棟,張偉,等.通過Java 調用本地方法[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下直接讀取軟盤扇區(qū)[J].電腦知識與技術,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ǒng)的設計與實現[J].微型機與應用,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技術的組態(tài)軟件數據庫訪問方法及應用[J].工業(yè)控制計算機,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].四川文理學院學報(自然科學版),2007,17(2):53-54.]