JAVA在IDEA中用JNA调用C++的dll动态链接库案例

目录

引言

准备工作

1、JNA包

2、DLL文件

接口准备

接口参数说明

1、DLL路径

【报错】找不到文件

如何查看DLL文件的位数?

2、函数声明

如何查看DLL文件中有哪些函数?

C++与JAVA JNA类型映射关系

函数调用

1、IntByReference

2、Pointer


引言

最近在做一个项目,需要用到已经写好的C++底层算法,该算法已经被打包成DLL,Java直接调用即可,难点在于C++与JAVA JNA类型映射关系。

准备工作

1、JNA包

这里提供jar包链接,可以直接下载,版本为4.0.0;

Maven仓库坐标为:


    net.java.dev.jna
    jna
    4.0.0

2、DLL文件

可以由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);
}

接口参数说明

1、DLL路径

Native.loadLibrary("PropegationFunDLL", Clibrary.class)

参数1:dll文件的路径,可以是绝对路径,也可以是相对路径,若把dll放在src文件夹下,可以直接使用“文件名”作为路径。

参数2:接口名.class

【报错】找不到文件

1、可能是路径错误;

2、可能是dll位数与jdk位数不匹配,32位一般使用jdk8,最新版本的jdk一般都是64位的。

dll位数与jdk位数必须要匹配!

如何查看DLL文件的位数?

在VS命令提示符中输入:dumpbin -headers dll文件名

如图,x86表示该dll为32位,x64表示dll为64位。

JAVA在IDEA中用JNA调用C++的dll动态链接库案例_第1张图片

2、函数声明

接口中函数名要与dll中的函数名一致

如何查看DLL文件中有哪些函数?

在VS命令提示符中输入:dumpbin -exports dll文件名

JAVA在IDEA中用JNA调用C++的dll动态链接库案例_第2张图片

该方法也可用于检查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文件名检查。

C++与JAVA JNA类型映射关系

在接口中声明函数时,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中函数修改后的数据:

1、IntByReference

以IntByReference row = new IntByReference()为例,获取方式为:

int rowIndex = row.getValue();

2、Pointer

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数组。

你可能感兴趣的:(Java后端技术,intellij-idea,c++,java,jar)