目录
引言
准备工作
1、JNA包
2、DLL文件
接口准备
接口参数说明
1、DLL路径
【报错】找不到文件
如何查看DLL文件的位数?
2、函数声明
如何查看DLL文件中有哪些函数?
C++与JAVA JNA类型映射关系
函数调用
1、IntByReference
2、Pointer
最近在做一个项目,需要用到已经写好的C++底层算法,该算法已经被打包成DLL,Java直接调用即可,难点在于C++与JAVA JNA类型映射关系。
这里提供jar包链接,可以直接下载,版本为4.0.0;
Maven仓库坐标为:
net.java.dev.jna
jna
4.0.0
可以由VS或者VC等软件生成,注意dll的位数,32位与64位调用时有区别。
该接口继承Library,用于加载DLL库文件,下面贴出项目中的代码。
package algorithmV10_0;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.DoubleByReference;
import com.sun.jna.ptr.IntByReference;
//继承Library,用于加载库文件
public interface Clibrary extends Library {
Clibrary INSTANTCE = (Clibrary) Native.loadLibrary("PropegationFunDLL", Clibrary.class);
Boolean Fun_CDBWB(String path_DEM, double latt, double lont, double latr, double lonr, double f,
double pt, double htg, double hrg, double[] Gt, double[] Gr,
IntByReference IDCode, DoubleByReference E, DoubleByReference L, DoubleByReference T);
Pointer Fun_CDBWB_Zone(String path_DEM, double latt, double lont, double[] latrlim, double[] lonrlim, double f,
double pt, double htg, double hrg, double[] Gt, double[] Gr, int scalefactor,
IntByReference IDCode, IntByReference row, IntByReference col);
}
Native.loadLibrary("PropegationFunDLL", Clibrary.class)
参数1:dll文件的路径,可以是绝对路径,也可以是相对路径,若把dll放在src文件夹下,可以直接使用“文件名”作为路径。
参数2:接口名.class
1、可能是路径错误;
2、可能是dll位数与jdk位数不匹配,32位一般使用jdk8,最新版本的jdk一般都是64位的。
dll位数与jdk位数必须要匹配!
在VS命令提示符中输入:dumpbin -headers dll文件名
如图,x86表示该dll为32位,x64表示dll为64位。
接口中函数名要与dll中的函数名一致
在VS命令提示符中输入:dumpbin -exports dll文件名
该方法也可用于检查DLL是否正确生成,但无法查看函数形参有哪些,若想知道形参只能查看C++源码。
补充:
1、VS 2017 新建空项目就能生成DLL文件,项目属性--配置属性--常规,目标文件扩展名:.dll,配置类型:动态库(.dll),设置完后生成解决方案即可生成DLL。
2、在用VS生成DLL时,最好采用.def模块定义文件生成,文件中依次列出需要导出的函数名,如:
LIBRARY PropegationFunDLL
EXPORTS
ADD @1
Fun_CDBWB @2
Fun_CDBWB_Zone @3
模块定义文件配置:项目属性--配置属性--链接器--输入--模块定义文件--选择模块
不采样模块定义文件的话,可能会出现导出的dll中,函数名与源码中的函数名不一致,函数名乱码,调用时无法准确调用对应函数的情况。可以用dumpbin -exports dll文件名检查。
在接口中声明函数时,java中使用的变量类型要与C++中的变量类型对应,否则无法正确从dll中传入或者取出数据。前文贴出的代码中的变量类型对应关系如下:
//Java
Boolean Fun_CDBWB(String path_DEM, double latt, double lont, double latr, double lonr, double f,
double pt, double htg, double hrg, double[] Gt, double[] Gr,
IntByReference IDCode, DoubleByReference E, DoubleByReference L, DoubleByReference T);
//C++
bool Fun_CDBWB(char path_DEM[255], double latt, double lont, double latr, double lonr, double f,
double pt, double htg, double hrg, double* Gt, double* Gr,
int *IDCode,double *E,double *L,double *T);
//Java
Pointer Fun_CDBWB_Zone(String path_DEM, double latt, double lont, double[] latrlim, double[] lonrlim, double f,
double pt, double htg, double hrg, double[] Gt, double[] Gr,
int scalefactor, IntByReference IDCode, IntByReference row, IntByReference col);
//C++
double* Fun_CDBWB_Zone(char path_DEM[255], double latt, double lont, double latrlim[], double lonrlim[], double f,
double pt, double htg, double hrg, double* Gt, double* Gr,
int scalefactor,int *IDCode,int *row,int *col);
在基本类型的使用上,java与C++区别不大,强调几个特殊的对应关系:
1、String <---> char[]
C++中的char[],在java中可以用String对应。
理论上java中用char[]数组也能对应(C++中的基本类型数组,java中都能用对应的基本类型数组对应),没有试过,String更方便。
2、Pointer <---> double* double[] <---> double* DoubleByReference <---> double*
C++中最复杂的就是对指针的应用,根据指针的不同应用场景,在java中对应的类型也有所不同。以double类型为例,作解释(int,float等类型同理):
A. double[] <---> double*
此时的double*在C++源码中,作为数组使用,且在函数中仅做读操作,即不指望该参数传出数据,此时在java中可以使用double[]数组对应。
B. Pointer <---> double*
此时的double*在C++源码中,作为数组使用,且在函数中做写操作,即希望在函数调用完成后从中能读取数据,此时在java中需要使用JNA包中的Pointer类型对应。
该情境不能使用double[],如果在调用dll时将double[] x传入,即便dll中的函数对x进行了修改,调用完成后,java也无法从x中读取dll修改后的数据。double[]类型的数据,传入是什么样,传出就是什么样,不受dll中的函数影响。
C. DoubleByReference <---> double*
此时的double*在C++源码中,作为单个浮点类型使用,且在函数中做写操作,此时在java中需要使用JNA包中的DoubleByReference类型对应。
区别总结:
Pointer与DoubleByReference:前者是“引用类型”的浮点型数组,后者是“引用类型”的单个浮点型数据。
Pointer与double[]:前者是“引用类型”的浮点型数组,后者是普通类型的浮点型数组。
此处的“引用类型”与java本身的引用类型概念不同,此处“引用类型”表示被dll中的函数调用后,函数中若出现修改操作,函数调用后java是否可读。
通过 接口名.INSTANTCE.函数名 调用函数。
IntByReference IDCode = new IntByReference();
IntByReference row = new IntByReference();
IntByReference col = new IntByReference();
Pointer res = Clibrary.INSTANTCE.Fun_CDBWB_Zone(path_DEM, latt, lont, latrlim,
lonrlim, fre, pt, htg, hrg, Gt, Gr, scalefactor,
IDCode, row, col);
if (IDCode.getValue() != 0)
throw new RuntimeException("错误码:" + IDCode.getValue() + ",请检查参数,传入参数有误!");
if (!resultDataIni) {
rowIndex = row.getValue();
colIndex = col.getValue();
resultData = new double[rowIndex][colIndex];
resultDataIni = true;
}
double[] resArray = res.getDoubleArray(0, rowIndex * colIndex);
重点关注如何从“引用类型”中获取被dll中函数修改后的数据:
以IntByReference row = new IntByReference()为例,获取方式为:
int rowIndex = row.getValue();
Pointer中存放的为数组数据,以Pointer res为例,获取方式为:
double[] resArray = res.getDoubleArray(0, rowIndex * colIndex);
Pointer有getDoubleArray(),getIntArray(),getFloatArray()等不同方法,根据源码中指针类型选择对应的方法,方法中,参数1和参数2可以理解成数组的startIndex和length。
如:res.getDoubleArray(0, rowIndex * colIndex),表示从0索引处开始取,取出长度为rowIndex * colIndex的double数组。