Java调用DLL实现读写磁盘扇区
2013-07-03张美玲
张美玲,张 刚
(太原理工大学 信息工程学院,山西 太原 030024)
0 引 言
Java是一种面向对象的编程语言,它虽然具有语法简单,面向对象,稳定,可移植性高等优点,但Java[1]以其跨平台的目的,使得它不能像其它语言(如C和汇编)那样更接近操作系统,和本地机器的各种内部联系变得很少,也就不能和操作系统的底层打交道了。为了解决Java与底层的交互,本文引入JNI技术,通过Java对本地方法的调用,实现应用层对磁盘的直接操作。JNI的提出主要有以下几个原因[2-3]:第一,当标准的java的类库不支持程序所需特性时,可以用其它语言实现的接口。第二,需要用底层语言实现一个小型的时间敏感代码时,考虑到java运行速度要比C/C++慢,需要引入JNI。第三,已经有了一个用其他语言写成的库或程序时,可以用java直接来调用,减少工作量。本文是基于java的类库不支持程序所需特性的原因,用C++编写读写扇区本地代码并生成DLL[4](WINDOWS平台下是.DLL 文件,Linux平台下是.SO 文件)文件。虽然在DOS环境下,通过中断或IO[5]可以直接对硬盘进行操纵,因为BIOS和DOS系统为磁盘操作提供了INT13H 中断,通过INT13H的读写功能可以实现磁盘的直接读写。但在Windows环境下,Win32系统禁止应用程序对硬盘直接操纵,禁止使用BIOS 中断。所以,Windows操作系统下,在应用层直接读取硬盘扇区变的困难。但并不意味着在windows环境下无法访问硬盘。它在采取了“实保护”的同时,也提供了一些API函数,本文通过ReadFile()和WriteFile()函数,以扇区存取的方式在本地代码中实现读写扇区,最后通过JNI方法实现了java对磁盘扇区的读写操作。
1 JNI技术原理及实现流程
1.1 JNI技术原理
目前java与dll交互的技术主要有3种:jni,jawin和jacob。JNI是sun公司提供的java与系统中的原生方法交互的技术(在windows\linux 系统中,本文是基于windows平台),它是JDK的一部分,提供了java与本地非java语言代码的接口,通过使用JNI编写的程序才能够确保代码移植到所有的平台。该平台相关代码是通过JNI函数来访问Java虚拟机功能的,而JNI函数需要通过第一个接口指针JINEnv*[6]获取。接口指针是指针的指针,它先指向一个JNI函数指针数组,而指针数组中每个元素又指向JNI接口函数。需要注意的是,本地方法将JNI接口指针当做参数来传递,所以在一个线程中对本地方法的多次调用,需要保证接口指针是相同的。但是,如果一个方法被不同的线程调用,需要不同的JNI接口指针。以下是JNI原理图,如图1所示。
图1 JNI原理
1.2 实现步骤
JNI实现的最终目标是要通过编写头文件及本地程序,结合C/C++文件生成动态库文件,最后加载到java程序运行成功。具体实现步骤[7]如图2所示。
图2 JNI实现步骤
下面结合实例具体分析JNI调用过程。
2 应用实例
该实例结合结合java对dll文件调用方法,在VC++中编写本地代码,并编译生成.dll文件,通过并java 对.dll文件调用,实现了java对磁盘扇区的直接读写。以下是VC++中实现读写扇区基本原理以及java实现读写扇区的基本步骤。
2.1 VC++读写扇区实现原理[8]
首先,使用CreateFile函数打开磁盘驱动,指定所要操作磁盘并设置读或写操作,该函数参数设置如下:
打开的文件名参数设置:对于读写扇区,如果访问的是具体某个逻辑分区,则文件名格式为“\\.\X”,如果访问的是第一个逻辑硬盘,则文件名格式为“\\.\PHYSICALDRIVE0”;文件的操作属性设置:允许读设备操作设置为GENERIC_READ,允许写设备操作设置为GENERIC_WRITE;文件共享属性设置:FILE_SHARE_READ和FILE_SHARE_WRITE分别表示允许对设备进行读共享访问和写共享访问;文件操作设置为OPEN_EXISTING,对于该设置文件必须已经存在,由设备提出要求,若该文件不存在,则函数调用失败。
其次,因为所要读的是磁盘中某个扇区,而打开的是整个磁盘,所以要通过SetFilePointer函数设置文件指针到磁盘中所要操作的某个扇区位置,CreateFile函数参数设置如下:
其中文件句柄是CreateFile函数所返回的句柄,如果该句柄值表示磁盘打开成功,则通过设置字节偏移量将指针指定到所要操作扇区,对于读写扇区操作,将字节偏移量设置为指针移动的字节数;文件定位设置为FILE_BEGIN,即从文件开始为参考位置进行读写。
接着,就可以利用ReadFile和WriteFile从指定位置读写扇区,该函数由五个参数组成,参数设置如下:
第一个参数为文件句柄,同上。第二个参数为缓冲区,表示用于保存读/写入数据的一个缓冲区。第三个参数为要读或写入的字符数,此处设置为从文件中读或写入的数据字节数。第四个参数为从文件中实际读或写入的字节数的指针。第五个参数设为NULL。
最后,完成访问操作后,如果读或写扇区失败,显示错误信息;如果读或写扇区成功,则用CloseHandle()函数关闭文件句柄,从而完成一次完整的磁盘扇区读写操作访问,具体操作流程如图3所示。
图3 流程
2.2 具体实现步骤
以上是在VC++环境下实现读写扇区的方法,而要想在应用层实现底层磁盘的操作,需要通过java来对本地方法进行调用,下面以磁盘数据读写为例,先在磁盘中写入数据,再通过读扇区的方式读取磁盘信息,分析了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,表示所读或写扇区号,这里是逻辑扇区号,此参数用来在设置文件指针位置时指定到所要读或写的扇区。data表示写入扇区中数据。返回参数类型均为布尔类型。由于Java和C的编码方式不同,所以JNI技术最关键部分就是参数的传递,即将本地代码中的参数转换为java可调用的参数类型,JNI数据类型映射见表1。
表1 JNI数据类型映射
native关键字作用:声明本地化方法。它告诉Java 编译器,方法是用Java类之外的本机代码实现的,不需要用Java代码具体实现,但其声明却在Java中。
(2)加载动态库
Writesector.java和Readsector.java中分别加载write-sector1和readsector1文件。Load关键字:声明的本地方法没有实现,但是我们在下面就直接使用了,所以必须在使用之前对它进行初始化。这里一般是以static块进行加载的。其中“writesector1”和“readsector1”是动态库的名字,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 表示函数的链接方式,当程序执行时从本地库文件中找函数,JNICALL 表示调用约定,说明调用的是本地方法。
以下是readSector函数中的主要部分:
第4步:使用VC++编译器编译.cpp,生成readsector1.dll文件。
3 代码测试及结果输出
(1)在Writesector.java中输入测试代码:
Writesector sample=new Writesector();
boolean bool=sample.writeSector(2149033,0xBB);
System.out.println("writeSector:"+bool);
为了方便测试,在逻辑扇区号为2149033的扇区中输入同一个数值0xbb。
在Readsector.java中输入测试代码:
Readsector sample=new Readsector();
boolean bool=sample.readSector(2149033);
System.out.println("readSector:"+bool);
为了检验写扇区的正确性,检验逻辑扇区号为2149033的扇区值是否正确。
(2)将readsector1.dll拷贝到Readsector.java所在的目录下,将writesector1.dll拷贝到Writesector.java 所在的目录下。
(3)运行Writesector.java,实现写扇区,输出结果如图4所示。
图4 读扇区
再运行Readsector.java,读取所写入扇区值。输出结果如图5所示。
图5 写扇区
4 Java调用本地方法准则及缺点
4.1 Java调用本地方法的准则
Java所调用的本地方法是指包含在特定平台下的可执行文件中,就本文示例而言,本地方法即包含在windows平台下的动态链接库DLL中。在java对本地方法的实际调用过程中,需考虑一下两个准则:
(1)当本地代码有多个方法时,可以将这些本地方法都封装到单个类中,这个类只需要调用一个DLL,即可实现对本地代码的调用。对于每种目标操作系统,只需要修改基于该平台的本地代码来替换DLL,这就将本地代码的影响减小对最小,也有助于不同平台下的一直问题。
(2)本地方法要简单。目的要使第三方运行时对DLL依赖程度减到最小,从而使本地方法更加独立,减小加载DLL和应用程序的开销。
4.2 Java调用本地方法的缺点
(1)Java作为一种面向对象的编程语言,虽然具有跨平台等优点,但JNI方法在实现java与本地代码交互的同时也限制了java语言的一个优点:程序的可移植性。java调用本地方法时,需要本地代码为其提供动态链接库,而链接库本身是与平台相关的。
(2)JNI方法使程序的安全性降低。JVM 给Java代码提供了完善的安全机制使得Java代码不会导致程序崩溃、滥用数据等,一旦使用了JNI,这种安全机制就无能力了。
(3)必须确保本地代码的稳定性,因为本地代码运行时可能会造成错误指针带来的间接错误,这样本地代码带来的丝毫错误都可能导致java虚拟机的崩溃[11]。
5 结束语
本文分析了基于windows平台下用VC++实现磁盘扇区的读写方法之后,通过JNI技术实现了java对VC++下生成的dll文件的调用,从而完成应用层对磁盘的直接访问,实现了java对系统底层的直接操作。由于java标准的类库无法支持与硬件的交互,这就受限了JNI方法的使用。而JNI方法在
实现java与本地代码双向交互的同时,使得程序本身丧失了跨平台的优点。所以,在使用JNI方法之前,一定要审查是否有更好的方法结合到java中。本文是在windows平台实现了对系统底层的一些操作,如果想要跨平台实现,这就要求在不同的操作系统下重新编译本地代码,通过使用JNI技术可以实现更为广泛的应用层与底层之间的交互,有待进一步研究。
[1]Eckel.Thinking in java 4[M].Beijing:Publishing House of Electronics Industry,2011(in Chinese).[埃克尔.java编程思想第四版[M].北京:电子工业出版社,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技术在软件开发中的应用研究[J].兰州工业高等专科学校学报,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技术在嵌入式软件开发中的应用[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开发数据库应用功能的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下直接读取软盘扇区[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平台的可视对讲系统的设计与实现[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技术的组态软件数据库访问方法及应用[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].四川文理学院学报(自然科学版),2007,17(2):53-54.]