Java动态调用DLL
最近有个项目,需要动态给系统里面添加dll,并且能动态调用。简单点来说,就是可以上传一个dll,一个说明文件,然后就可以执行dll里面的函数了。配置文件当然是用来描述dll里面的函数的。
怎么实现呢?首先是JNA调用DLL,大致的代码如下:
看起来是不是很简单,对就是这么简单。但是问题来了,这种调用方式,需要每次手动定义一个继承自Library的接口。就是必须先定义,才能用。跟项目要求有点出入。
想到的解决方法,方法1,既然必须定义接口,那就先生成一个java文件,然后调用javac编译,然后再加载这个class文件,达到目的。但是这个貌似比较麻烦。
方法2:动态生成一个接口,就是根据dll描述文件,动态生成一个接口类,然后再调用。这个方式貌似比较简单。这里就用这种方式,使用到的工具包是javassist,就是专业动态创建类和对象用的工具。
具体实现的效果见下图:
项目结果见下图:
下面直接上代码,首先是几个解析用的类,可以直接复制,粘贴到自己的包里面。包名记得改。
package com.qujia.dll.bean;import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;import com.sun.jna.Native;
import com.sun.org.apache.bcel.internal.generic.MULTIANEWARRAY;import javassist.CtClass;/*** 定义一个动态接口* @author qujia**/
public class MyInterface {public static String packageName="com.dajia.function.c.";//基础包名String name;//类名List<Function> functions=new ArrayList<Function>();//函数String dll;//dll路径Class clz;//动态构建的类Object instance;//动态创建的对象/*** 从dll文件创建* @param dll*/public MyInterface(String dll) {this.dll=dll;dll=dll.replace("\\", "/").replace(".", "_").replace("-", "_");//去除路径的特殊符号String arr[]= dll.split("/");//分割一下String className=arr[arr.length-1];//取出文件名称String forder=arr[arr.length-2];//取出文件名称this.name=packageName+forder+"."+className;//报名,加上文件夹,加上文件名防止冲突System.out.println("interface name "+this.name);}/*** 添加一个函数* @param name* @return*/public Function addFunction(String name) {Function f=new Function(name);functions.add(f);f.setParent(this);return f;}/*** 添加一个函数* @param name* @return*/public Function addFunction(String name,String type) {Function f=new Function(name,type);f.setParent(this);functions.add(f);return f;}public void clean() {if(instance!=null) {instance=null;//释放对象}}/*** @return the name*/public String getName() {return name;}/*** @param name the name to set*/public void setName(String name) {this.name = name;}/*** @return the functions*/public List<Function> getFunctions() {return functions;}/*** @param functions the functions to set*/public void setFunctions(List<Function> functions) {this.functions = functions;}/*** @return the clz*/public Class getClz() {return clz;}/*** @param clz the clz to set*/public void setClz(Class clz) {this.clz = clz;}/*** @return the instance*/public Object getInstance() {return instance;}/*** @param instance the instance to set*/public void setInstance(Object instance) {this.instance = instance;}/*** 创建执行对象*/public void create() {if(clz==null) {clz=InterfaceCreater.create(this);instance=Native.load(dll, clz);for(Function f :functions)f.create();//初始化一下}}/*** 从接口直接执行* @param func 函数名称* @param params 参数* @return*/public Object exec(String func,Object...params) {if(clz==null)return null;for(Function f :functions) {if(f.getName().equals(func)) {return f.exec(params); }}return null;}}
package com.qujia.dll.bean;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;import javassist.ClassPool;
import javassist.CtClass;
/*** 用来存储一个函数* @author qujia**/
public class Function {String name;//函数名称String returnType;//返回值List<Param> params=new ArrayList<Param>();//参数列表MyInterface parent;//所属接口,就是父亲Method mtd;//指向动态接口的方法指针public Function() {returnType="void";}public Function(String name) {this.name=name;returnType="void";}public Function(String name,String returnType) {this.name=name;this.returnType=returnType;}public Function addParam(String name,String type) {this.params.add(new Param(name,type));return this;}public Function addParam(String name,String type,boolean require) {this.params.add(new Param(name,type,require));return this;}/*** @return the name*/public String getName() {return name;}/*** @param name the name to set*/public void setName(String name) {this.name = name;}/*** @return the returnType*/public String getReturnType() {return TypeMap.getType( returnType);}/*** @param returnType the returnType to set*/public void setReturnType(String returnType) {this.returnType = returnType;}/*** @return the params*/public List<Param> getParams() {return params;}/*** @param params the params to set*/public void setParams(List<Param> params) {this.params = params;}/*** @return the parent*/public MyInterface getParent() {return parent;}/*** @param parent the parent to set*/public void setParent(MyInterface parent) {this.parent = parent;}/*** 返回参数类型数组* @return*/public Class[] getParamsClass() {try {Class[] cls=new Class[params.size()];for(int i=0;i<params.size();i++) {//System.out.println("param "+name +" type "+ params.get(i).getType() );cls[i]=params.get(i).getClas();//System.out.println("param "+name +" type "+cls[i].getTypeName());}return cls;}catch (Exception e) {// TODO: handle exceptione.printStackTrace();return null;}}/*** 初始化反射对象*/void create() {if(mtd!=null)return;if(parent.getClz()==null)return;//没有父类,不操作try {Class[] cs=getParamsClass();//System.out.println("function "+name +" params "+cs.toString());mtd=parent.getClz().getDeclaredMethod(name, cs);//寻找函数}catch (Exception e) {// TODO: handle exceptione.printStackTrace();}}/*** 执行函数* @param val* @return*/public Object exec(Object ... val) {if(mtd==null) return null;try {return mtd.invoke(parent.getInstance(),val);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();return null;}}}
package com.qujia.dll.bean;
/*** 函数参数定义* @author qujia**/
public class Param {String name;//参数名称,实际没啥用String type;//参数类型Object val;//参数值,也没啥用boolean require;//是否必须,目前也没啥用public Param() {type="int";require=false;}public Param(String name) {this.name=name;type="int";require=false;}public Param(String name,String type) {this.name=name;this.type=type;require=false;}public Param(String name,String type,boolean require) {this.name=name;this.type=type;this.require=require;}/*** @return the name*/public String getName() {return name;}/*** @param name the name to set*/public void setName(String name) {this.name = name;}/*** @return the type*/public String getType() {return TypeMap.getType(type);}public Class getClas() {return TypeMap.getClass(type);}/*** @param type the type to set*/public void setType(String type) {this.type = type;}/*** @return the val*/public Object getVal() {return val;}/*** @param val the val to set*/public void setVal(Object val) {this.val = val;}/*** @return the require*/public boolean isRequire() {return require;}/*** @param require the require to set*/public void setRequire(boolean require) {this.require = require;}}
package com.qujia.dll.bean;import java.util.HashMap;
import java.util.Map;
/*** 用于类型映射,字符串转换为java类型* @author dell**/
public class TypeMap {static Map<String,Class> types=new HashMap<String,Class>();//存储类型的集合/*** 获取类型名称* @param key 类型字符串* @return*/public static String getType(String key) {if(types.containsKey(key))return types.get(key).getTypeName();return key;}/*** 获取类型 Class* @param key 类型名称字符串* @return*/public static Class getClass(String key) {if(types.containsKey(key))return types.get(key);try {return Class.forName(key);}catch (Exception e) {// TODO: handle exceptionreturn null;}}/*** 初始化常用类型,其他类型可以继续增加*/static {types.put("int", int.class);types.put("integer", int.class);types.put("Integer", int.class);types.put("float", float.class);types.put("double", double.class);types.put("String", String.class);types.put("bool", boolean.class);types.put("string", String.class);}}
package com.qujia.dll.bean;import java.util.ArrayList;
import java.util.List;import com.sun.jna.Library;import javassist.*;
/*** 用于创建通态接口的工具* @author qujia**/
public class InterfaceCreater {/*** 创建动态接口* @param it 自定义的接口类* @return*/static Class create(MyInterface it) {try {ClassPool pool = ClassPool.getDefault();// 创建一个新的接口CtClass dynamicInterface = pool.makeInterface(it.name);//创建一个接口名称dynamicInterface.setSuperclass(pool.getCtClass("com.sun.jna.Library"));//要集成自这个类for(Function f : it.functions) {//变量函数CtClass ps[]=new CtClass[f.params.size()];//参数列表for(int i=0;i<f.params.size();i++) {ps[i]=pool.get(TypeMap.getType( f.params.get(i).type ));//参数}//创建函数CtMethod mtd = new CtMethod(pool.get( TypeMap.getType( f.returnType)), //返回值f.name, //名称ps, //参数dynamicInterface //所属动态接口);System.out.println("create method "+f.name);mtd.setModifiers(Modifier.PUBLIC | Modifier.ABSTRACT);//必须是抽象和publicdynamicInterface.addMethod(mtd);//添加到动态接口}return dynamicInterface.toClass();//返回接口的Class}catch (Exception e) {// TODO: handle exceptione.printStackTrace();return null;}}}
下面的测试代码。
package com.qujia.dll;import com.qujia.dll.bean.Function;
import com.qujia.dll.bean.MyInterface;/*** 测试代码**/
public class App
{public static void main( String[] args ){testGcc();//testPID();}static void testPID( ){double height=10;double opened=50;//开度double target=60;//目标高度 MyInterface mi=new MyInterface("E:\\project\\pid1\\pid_control.dll");//int C_PIDController_TxtImporter(const char* path)Function f1=mi.addFunction("C_PIDController_TxtImporter","int").addParam("path", "String");//初始化函数//double C_PIDController_GetOutputValue(double setpoint,double input)Function f2=mi.addFunction("C_PIDController_GetOutputValue","double").addParam("setpoint", "double").addParam("input", "double");//pid算法函数mi.create();Object r=f1.exec("E:\\project\\pid1\\config.txt");//初始化System.out.println("初始化结果 "+r);//初始化为1表示成功int i=0;while(true) {//循环调用i++;opened=(double)f2.exec(target,height);//调用pid算法 height=height-5+opened/100*20; //液位等于当前液位减去一个恒定的排放量+阀门开度/100*满开时的流量 System.out.println(String.format("i=%d , 液面高度=%f , 目标高度 %f , 开度 =%f ", i,height,target,opened));//输出try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}static void testGcc() {MyInterface mi=new MyInterface("E:\\project\\gccdll\\test.dll");//int init(String path)mi.addFunction("init","int").addParam("path", "String");//初始化函数//int add(int a,int b)mi.addFunction("add","int").addParam("a", "int").addParam("b", "int");//add//int getCur()mi.addFunction("getCur","int");////int curAdd(int v)mi.addFunction("curAdd","int").addParam("v", "int");////int curAdd2(int i,int v)mi.addFunction("curAdd2","int").addParam("i", "int").addParam("v", "int");////int get(int i)mi.addFunction("get","int").addParam("i", "int");//mi.create();int a=9;int b=9;int r=(int)mi.exec("add", a,b);//函数名称方式调用System.out.println("a+b="+r);r=(int)mi.exec("init", "E:\\project\\gccdll\\p1.txt");System.out.println("初始化结果 "+r);r=(int)mi.exec("init", "E:\\project\\gccdll\\p2.txt");System.out.println("初始化结果 "+r);r=(int)mi.exec("init", "E:\\project\\gccdll\\p3.txt");System.out.println("初始化结果 "+r);r=(int)mi.exec("getCur");//System.out.println("当前值 "+r);r=(int)mi.exec("curAdd",99);//System.out.println("结果 "+r);r=(int)mi.exec("curAdd2",1,500);//System.out.println("结果 "+r);r=(int)mi.exec("get",1);//System.out.println("结果 "+r);}}
最后是用的依赖,一个是JNA,一个是javassist,maven依赖如下:
<dependency><groupId>net.java.dev.jna</groupId><artifactId>jna</artifactId><version>5.12.1</version></dependency><dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.29.2-GA</version></dependency>
测试用的C代码如下:需要用gcc 编译
#include <stdio.h>
#include <stdlib.h>
int a[100];
int cur=0;
int add(int a, int b){int t;t = a + b;return t;
}int init(const char* f){FILE *file = fopen(f, "r");if (file == NULL) {printf("文件打开失败!\n");return -1;}int num;while (fscanf(file, "%d", &num) != EOF) {printf("%d\n", num); // 输出整数}fclose(file);a[cur]=num;cur++; return cur-1;
}int getCur(){return cur;
}int curAdd(int v){a[cur]=a[cur]+v; return a[cur];
}
int curAdd2(int i,int v){a[i]=a[i]+v; return a[i];
}int get(int i){
return a[i];
}