技术领域
[0001] 本发明涉及操作系统技术领域,具体涉及一种在windows操作系统环境下Java调用DLL的便捷方法。
相关背景技术
[0002] 目前,Java中调用DLL常用方法大致有JNI、JNA、JNative等。这些方法的差异如下:JNI使用的人比较多,但是相对来说比较麻烦,要熟悉C语言并且要使用Javac和Javah命令,步骤繁琐;JNA、JNative只需要实现接口,相对于JNI是比较简单便捷;JNative调用第三方DLL的时候需要先预先写好繁琐初始化方法实现接口,这将是目前亟待需要解决的问题所在。
具体实施方式
[0051] 为使本发明实施例的目的、技术方案和优点更加清楚,下面将结合本发明实施例中的附图,对本发明实施例中的技术方案进行清楚、完整地描述,显然,所描述的实施例是本发明一部分实施例,而不是全部的实施例。基于本发明中的实施例,本领域普通技术人员在没有做出创造性劳动前提下所获得的所有其他实施例,都属于本发明保护的范围。
[0052] 实施例
[0053] 本发明公开了一种在windows操作系统环境下,使用Java语言调用DLL的方法,具体流程步骤如附图1所示,过程如下:
[0054] 步骤S1、基于Windows操作系统,并且安装好Java的开发环境Jdk、Eclipse等;
[0055] 步骤S2、拥有一个JAVA项目,新建或者导入现有项目;
[0056] 步骤S3、在网上下载JNative的Jar包和DLL;
[0057] 步骤S4、JNative.jar导入到Java项目中,拷贝JNativeCpp.dll到Windows\System32目录中;
[0058] 步骤S5、根据第三方DLL动态库函数编写Java接口类;
[0059] 现有技术中,第三方DLL动态库函数,函数名CRC16(length、ptr),该函数的作用是对数据体进行计算得到校验码。函数拥有两个参数,分别是length、ptr。length数据类型是int,代表着数据长度。ptr数据类型是unisgned char指针,代表数据体。函数的返回值数据类型是int,代表数据体计算后的校验码。
[0060] 相对应的Java接口类函数,函数名CRC16(length、ptr),作用如上介绍。函数拥有两个参数,分别是length、ptr。length数据类型是int,代表数据长度。ptr数据类型是byte[]数组,代表数据体。函数的返回值数据类型是int,代表数据体计算后的校验码。
[0061] 以上可以看到第三方DLL动态库函数的数据体unsigned char指针类型是对应不上Java接口类的数据体byte[]类型,C语言中unsigned char数据类型的范围是0~255,Java的数据类型byte范围是-128~127。
[0062] 例如:十六进制0xF0,十进制240,比特数11110000,符合C语言中unsigned char的取值范围,超过了Java中byte的取值范围,但是调用JNative的时候会自动进行转换处理,用户只需要正常赋值即可。
[0063] 步骤S6、编写动态代理处理类;
[0064] (1)在编写动态代理处理类之前先预用步骤S5中Java接口类作为案例通过常规方式调用DLL,以下简要说明:
[0065] 1、Java接口类byte[]ptr数据类型对应第三方DLL动态库unsigned char指针数据类型。Java 中没 有指针的 概念 ,所以 必须 用J Nat ive .ja r包里 面org.xvolks.jnative.pointers.Pointer,根据数据体大小创建内存块空间。注意的是内存块空间的单位是以字节为单位的,如果Java接口类函数的参数为double[]ptr,内存块空间就需要数据体大小*8,因为double是占8个字节的。最后把Java接口类函数传递进来的参数byte[]ptr赋值到内存块中;
[0066] 2、声明JNative对象,构造函数传递动态库名称、函数名称,JNative m=new JNative(“动态库名”,“函数名”);
[0067] 3、设置返回值类型m.setRetVal(Type.Int);
[0068] 4、设置参数,把已赋值的内存块空间与int length直接设置到JNative的参数列表中m.setParameter(下标,数据)。下标指的是对应的参数顺序号,数据指的是需要传递进去的数据体;
[0069] 5、调用m.invoke(),成功后获取返回值m.getRetVal()。
[0070] 可以看到通过常规的方式调用DLL是非常繁琐的事情,如果传递多个指针参数的时候会需要多次进行开辟内存赋值内存等操作,如果有N个非常复杂的函数情况下,完全就是无用功的拷贝粘贴工作。为了解决该问题,这里提出一个构想,只管Java接口类的编写,并不管接口的具体实现(这里具体实现只的是以上一大串繁琐的操作),所以这里需要编写动态代理处理类。
[0071] (2)动态代理处理类需要做到的实现效果:
[0072] 1、首先定义Java接口类,public interface IDataCrc。定义接口函数public int crc(int length,byte[]ptr);
[0073] 以上可以看到crc的函数,参数int length数据长度、byte[]ptr数据体,返回值类型是int;
[0074] 2、定义接口动态实现类,public class Native。定义函数public static Object loadLibrary(String dllName,String funName,String iCls);函数参数dllName代表动态库名称,funName代表动态库内对应的函数名称,iCls代表Java接口类(带完整包路径,如com.demo.IDataCrc),返回值是实现的Java接口类。
[0075] 3、具体调用方式IDataCrc iCrc=(IDataCrc)Native.loadLibrary(“动态库名称”,“函数名称”,“com.demo.IDataCrc”)。
[0076] 4、调用icrc.crc(1024,new byte[1024]),中间无任何复杂的繁琐的事情,直接动态后调用。
[0077] (3)动态代理处理类具体实现分为四大部分(接口反射、类文件写入、动态编译、实现接口)
[0078] S61、接口类反射,为什么要对接口类进行反射呢?是因为对接口类反射可获取到接口的所有参数的详细信息以及返回值信息,可作为类文件写入的前提条件,以下是具体操作步骤:
[0079] S611、根据Sring iCls Java接口类类名作为Class.forName参数,实现调用Class c=Class.forName(iCls);
[0080] S612、获取该类的所有函数,Method[]ms=c.getMethods();
[0081] S613、循环遍历获取函数中的函数名称m.getName()、参数数据类型mgetParameterTypes()、返回值m.getReturnType()。
[0082] S62、类文件写入,既然通过接口反射获取到参数和返回值的信息,就拿Method.GetName(),Method getParameterTypes(),Method.getReturnType()信息去作为动态类写入的判断条件,
[0083] 以下是具体实现:
[0084] S621、声明String aClassSrc变量、String sFun变量、String sParams变量、String sMemory变量、String sSetParams变量,aClassSrc代表类内容代码存储变量,sParams代表参数代码存储变量,sFun代表函数代码存储变量,sMemory代表内存块代码存储变量,sSetParams代表设置参数代码存储变量;
[0085] S622、写入包名、类名、继承关系、配对符合到aClassSrc变量中,注意继承关系必须是主入口loadLibrary的iCls,因为是要动态实现该接口;
[0086] S623、根据Method[]ms=c.getMethods()获取所有需要实现的函数,for循环遍历该数组;
[0087] S624、初始化sFun、sParams、sMemory、sSetParams变量,每次遍历是一个新的组合;
[0088] S625、获取Method[]ms遍历的项,Method m=ms[i];
[0089] S626、遍历m.getParameterTypes(),判断数据类型,如果是数组类型把Pointer分配内存块空间代码写入sMemory变量中,其他数据类型不需要做特殊处理。然后把数据类型和参数名称代码写入到sParams变量,遍历m.getParameterTypes(),结束前需要把设置参数代码写入到sSetParams变量;
[0090] S627、根据m.getName()和m.getReturnType()编写实现接口函数,sParams作为函数参数列表,sMemory作为实现分配内存块空间代码,sSetParams作为实现设置参数代码,整体组装写sFun变量中。组装顺序必须按照普通类去操作,函数入口public①返回值②函数名(③参数列表){④分配内存空间代码⑤调用JNative代码⑥设置参数代码⑦调用返回};
[0091] S628、最后把拼装好的sFun代码内容加入到aClassSrc,进行下次遍历,重新进入步骤S624;
[0092] S629、遍历完Method[]ms函数数组后,把aClassSrc作为文件内容写入到本地文件中,类名字必须是aClassSrc内容里的类名字,如:/src/com/demo/Test.java。
[0093] S63、动态编译,这里已经将类文件写入到本地中,后面就是对该文件进行动态编译,以下是具体实现:
[0094] S631、获取当前Java的编译器对象JavaCompiler,具体获取方式如:
[0095] JavaCompiler cmp=ToolProvider.getSystemJavaCompiler();
[0096] S632、获取Java标准文件管理器,具体获取方式如:
[0097] StandardJavaFileManager fileMgr=ompiler.getStandardFileManager(null,null,null);
[0098] S633、获取Java文件对象,具体获取方式如下:
[0099] Iterable units=fileMgr.getJavaFileObjects(fileName),fileName是刚保存aClassSrc的文件路径;
[0100] S634、设置编译环境,具体设置方式如下:
[0101] CompilationTask t=compiler.getTask(null,fileMgr,null,null,null,units);
[0102] S635、进行编译,t.call()如果没有报错则编译成功,关闭文件管理器对象fileMgr.close()。
[0103] S64、实现接口,动态编译完毕后,这里需要把编译后的反射进行实例化然后实现接口返回到调用者中,
[0104] 以下是具体实现:
[0105] S641、需要定位到该项目中,System.getProperty("user.dir")获取项目的路径,然后通过URL去定位项目中,实现方式如下:URL[]urls=new URL[]{new URL(“file:/”+System.getProperty(“user.dir”)+“/src”)};
[0106] S642、加载该项目中的类文件,实现方式如下:
[0107] URLClassLoader ul=newURLClassLoader(urls);
[0108] S643、反射刚刚编译的接口实现类,通过newInstance()函数声明对象,强制转换接口类,返回实现的接口类。
[0109] 步骤S7、调用动态代理处理类,把步骤S4的Java接口类作为参数传递进去;
[0110] public static void main(String[]args)
[0111] {IDataCrc iDataCrc=(IDataCrc)
[0112] Native.loadLibrary("DataCrc.dll","CRC16",“com.demo.IDataCrc”);}[0113] 步骤S8、调用完毕获取到已实现的接口;
[0114] 具体实现如下:iDataCrc.CRC16(length,ptr)。
[0115] 上述实施例为本发明较佳的实施方式,但本发明的实施方式并不受上述实施例的限制,其他的任何未背离本发明的精神实质与原理下所作的改变、修饰、替代、组合、简化,均应为等效的置换方式,都包含在本发明的保护范围之内。