此示例讲解了Java 使用 JNA 库去调用C语言生成的动态链接库,并与spring boot 做了一个简单整合
一、创建SpringBoot工程
这里使用的开发工具为IDEA 2019版本
使用IDEA官方工具来进行SpringBoot工程的创建,这里要注意选择JDK版本,推荐使用1.8
![0b54b75ff1fde400d70de52b93a2d4ac.png](https://i-blog.csdnimg.cn/blog_migrate/036b30b266e5e689c62ab95bcb40e9a1.jpeg)
![193e98067f7cb9443e161a98b6a6c406.png](https://i-blog.csdnimg.cn/blog_migrate/872412fc871b491486b2c8dc1e92e455.jpeg)
选择web项目 选择springweb模板,点击下一步则会创建一个SpringBoot项目
![d3842dab80d1b1fa4fa2c1768cd5329f.png](https://i-blog.csdnimg.cn/blog_migrate/245778bb011b65d58288bf55c987449d.jpeg)
项目工程结构如下图所示 我删除了一些没用的东西
![608a5ca1f838adb98979e70cada9e18b.png](https://i-blog.csdnimg.cn/blog_migrate/4deb8399ed24330670f76d580bd3aa27.png)
二、导入JAVA相关JAR包
首先在JAVA工程中要先导入JNA的JAR包,这里采用的是maven工程的方式,所以直接导入maven的依赖项即可,maven仓库地址为https://mvnrepository.com/artifact/net.java.dev.jna/jna
在工程目录pom.xml文件中加入下面依赖:
注意是在标签为后添加
<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.1.0</version>
</dependency>
![82f2d0fa9cd1aa78215f707234a30f30.png](https://i-blog.csdnimg.cn/blog_migrate/659acfb805298dbc4539c60f6b674365.png)
三、创建C语言动态链接库
导入依赖项以后,首先打开VS 2019(没有2019可以使用其他版本代替)创建一个C代码动态链接库程序,如下图所示
![b6f81b73db5a2a5c357518733a3d0533.png](https://i-blog.csdnimg.cn/blog_migrate/a78e201a5c0e895c887327ca4a45c24f.jpeg)
- 首先解释下C语言工程中头文件与主程序的对应关系,
![737450ec8b775e3bebac3018911e9623.png](https://i-blog.csdnimg.cn/blog_migrate/5ffa4ca5528e5c1ecc6ff7a1df2bf673.png)
C语言成功分为头文件与源文件两大块,源文件可以通俗的理解为写C逻辑函数的地方,头文件里面则是声明一些变量或者其他信息的地方,由成功目录可见有一个pch.cpp的源文件,有一个pch.h的头文件,打开pch.h头文件可以看到一些由VS自动生成的注解以及信息
![9a50f1909a36480414ad6a72fd19890f.png](https://i-blog.csdnimg.cn/blog_migrate/4f278fc856c0f562a7d403df6395c8b3.jpeg)
在头文件中写入以下代码,做一个简单的加法减法器函数
extern "C" _declspec(dllexport)int Add(int a, int b);
extern "C" _declspec(dllexport)int Sub(int a, int b);
最后在pch.cpp源文件中引入在头文件中声明的两个函数
![84a2faaf5d7db7fe5dc64483549bd85b.png](https://i-blog.csdnimg.cn/blog_migrate/625e81fbcf4168aef00e78c0aa908d59.png)
然后生成一个动态链接库
![bd20a1733713e5726c80c1ca53f435f5.png](https://i-blog.csdnimg.cn/blog_migrate/92221835f3828fbcb11628cd01132d3b.jpeg)
==注意在生成动态链接库之前一定要选择生成的链接库的位数,假如你的java jdk是64位的这里一定要选择X64进行对应,32位的则选择x86
![e1037d1c2c628405d22edbd00e4ab50b.png](https://i-blog.csdnimg.cn/blog_migrate/8787bf2f6dd37f2c7a1dd2a6a6208fb9.png)
生成成功后则会显示生成成功
![6badb0d69c15b14519267133944e3282.png](https://i-blog.csdnimg.cn/blog_migrate/16e63890973ace19903cfb4c024da84b.png)
四、创建HelloWord测试类进行动态链接库调用测试
代码如下所示
public class HelloWorld {
public interface CLibrary extends Library {
CLibrary INSTTANCE = (CLibrary) Native.loadLibrary("Dll3" , CLibrary.class);
int Add(int a,int b);
int Sub(int a,int b);
}
public static void main(String[] args) {
int sum = CLibrary.INSTTANCE.Add(3, 6);
int sub = CLibrary.INSTTANCE.Sub(3, 2);
System.out.println("sum = " + sum);
System.out.println("sub = " + sub);
}
}
下面我来解释下这段代码意思:
- 首先需要定义一个借口,继承
Library
或StdCallLibrary
,默认继承是Library
如果动态链接库里的函数是以stdcall方式输出的,那么就继承StdCallLibrary
,比如众所周知的kernel32库。比如上例中的接口定义:
public interface CLibrary extends Library {
}
- 接口内部定义
接口内部需要定义一个公共静态常量:INSTTANCE
(名字自取),通过这个常量,就可以获得这个接口的实例,从而使用接口的方法,也就是调用外部dll的函数,该接口通常通过Native.loadLibrary()这个API函数获得,该函数有2个参数:
- 第一个参数是动态链接库dll/so的名称,但不带.dll或.so这样的后缀,这符合JNI的规范,因为带了后缀名就不可以跨操作系统平台了。比如在示例代码中
Native.loadLibrary("Dll3" , CLibrary.class)
Native.loadLibrary
的第一个参数为 Dll3 及与生成的动态链接库函数同名,这样才能够找的到。 - 这里我说明一下,首先要将第三步生成的DLL库函数COPY到 JAVA 工程的resources目录下,不然会抛异常找不到
![0643e5f3fcebef2eb17faf78d8b1d8df.png](https://i-blog.csdnimg.cn/blog_migrate/1116d23a4e3c45788f46e7e76aa993d1.png)
- 第二个参数是本接口的Class类型。JNA通过这个Class类型,根据指定的.dll/.so文件,动态创建接口的实例。该实例由JNA通过反射自动生成。
接口中需要定义你要用到的函数或者公共变量,不需要的可以不定义,如上例中定义的 Add,Sub 两个函数
int Add(int a,int b);
int Sub(int a,int b);
这里要注意,在定义要使用的函数时候,函数名的大小写一定要与动态链接库中定义的函数名一致,因为它的内核是通过字符串进行反射匹配,假如大小写不一致或者说名字不一样或抛出异常,找不到该方法。
- 接口的调用使用
public static void main(String[] args) {
int sum = CLibrary.INSTTANCE.Add(3, 6);
int sub = CLibrary.INSTTANCE.Sub(3, 2);
System.out.println("sum = " + sum);
System.out.println("sub = " + sub);
}
![b90c358ae6191fbbdd21d4f0752e5d4d.png](https://i-blog.csdnimg.cn/blog_migrate/f586dfd6df120aaabcfc80dce02dde75.png)
五、基于Web应用的整体结合
- 前面我们已经创建了一个基于SpringBoot的web项目,在此基础上进行项目的搭建以及完善
- 在项目src下创建controller,service等包
- 在controller与service包中进行类的创建与代码的编写
- 将前端静态资源文件放置在resources下的static目录中
整体工程目录如下:
![382108b829eb16c52021c79fe50c05f9.png](https://i-blog.csdnimg.cn/blog_migrate/b3262ac193e3e0412d26ffab91a4d9ab.png)
- 在service已经controller中写入代码
在controller中贴入下方代码:
这里的代码我就不细细讲解了,后续再讲解
@RestController
public class HelloWorld {
@Autowired
HelloWorldService helloWorldService;
@Value("#{ new java.util.HashMap() }")
Map<String, Object> mapJson;
@RequestMapping(value = "/add", method = RequestMethod.POST)
public Map<String, Object> sum(@RequestBody Map<String, String> map) {
System.out.println("map = " + map);
int a = Integer.parseInt(map.get("a"));
int b = Integer.parseInt(map.get("b"));
int sum = helloWorldService.librayAdd(a, b);
mapJson.put("msg",sum);
return mapJson;
}
}
在service中贴如下方代码:
@Service
public class HelloWorldService {
@Autowired
DllCenter dllCenter;
public int librayAdd(int a, int b) {
return dllCenter.dllAdd(a, b);
}
}
在DllCenter中贴如下方代码
@Component
public class DllCenter {
public interface CLibrary extends Library {
CLibrary INSTTANCE = (CLibrary) Native.loadLibrary("Dll3" , CLibrary.class);
int Add(int a,int b);
int Sub(int a,int b);
}
public int dllAdd(int a, int b) {
return CLibrary.INSTTANCE.Add(a, b);
}
}
前端的页面代码以及js脚本代码较为简单,设计到两个文本输入框与ajax请求
启动服务器进行数值测试
![6edfd1f182cb3440150dfd9716a5f981.png](https://i-blog.csdnimg.cn/blog_migrate/de2031681314aa85a9e541ec977ccc4d.png)
![f7d4ba32aa98f8ae9b414465a12f26a0.png](https://i-blog.csdnimg.cn/blog_migrate/1d3d4b02c92ed8413647bb578fcea9d2.png)
这里仅测试了加法案例,减法也类似
源代码已经上传至码云
六、JNA调用C语言底层结构体进行参数赋值
- 整体工程结构基于上面工程,直接上代码,先上DLL中的代码
pch.h
头文件中的代码
#ifndef PCH_H
#define PCH_H
struct myStruct
{
int a;
int b;
};
extern "C" {
__declspec(dllexport) int addNormal(myStruct ms);
__declspec(dllexport) void addPrt(myStruct* ms, int* ret);
__declspec(dllexport) int addRef(myStruct& ms);
};
#endif //PCH_H
pch.cpp
源文件中的代码
#include "pch.h"
__declspec(dllexport) int addNormal(myStruct ms) {
return ms.b + ms.a;
}
__declspec(dllexport) void addPrt(myStruct* ms, int* ret) {
*ret = ms->a + ms->b + 1;
}
__declspec(dllexport) int addRef(myStruct& ms) {
return ms.a + ms.b + 2;
}
- Java 代码与前面的有所不同,整体架构类似,也是要先进行一个DLL库的读取加载
package com.lyh.jnademo.service;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;
import com.sun.jna.ptr.IntByReference;
import java.util.ArrayList;
import java.util.List;
/**
* @author lyh
* @version 1.0
* @date 2019/10/12
*/
public interface StructTest extends Library {
/**
* 创建一个接口实体用于获取DLL中的接口
*/
StructTest INSTANCE = (StructTest) Native.loadLibrary("dlltest", StructTest.class);
/**
* 定义一个接口内部类,继承Structure抽象类,
* 必须要实现两个接口,分别为下面的ByReference与ByValue和重写一个getFieldOrder方法
*/
public static class myStructur extends Structure {
public static class ByReference extends myStructur implements Structure.ByReference {
}
public static class ByValue extends myStructur implements Structure.ByValue {
}
//定义参数
public int b;
public int a;
/**
* 此方法用于获取DLL中的参数,来进行一个抽象封装
* @return 参数合集
*/
@Override
protected List<String> getFieldOrder() {
List<String> Field = new ArrayList<String>();
Field.add("a");
Field.add("b");
return Field;
}
}
int addNormal(myStructur.ByValue ms);
void addPrt(myStructur.ByReference pms, IntByReference sum);
int addRef(myStructur.ByReference rms);
/**
* 1.只要涉及到结构体的传递,必须使用ByReference或者ByValue中的一种
* 2.指针和引用的传递使用ByReference
* 2.拷贝参数传递使用ByValue
*/
public static void main(String[] args) {
//具体是使用引用传递还是值传递得看C底层的接收写法是什么样的
// addPrt(myStruct* ms, int* ret) 接收的参数类型为指针类型
//addRef(myStruct& ms) 接收的参数为引用类型所以都应该使用ByReference来进行值传递
myStructur.ByReference myStructur = new myStructur.ByReference();
myStructur.a=1;
myStructur.b=3;
//根据C代码的结构来定义一个整数接收器(用于接收底层计算出的结果,通过.getValue的方法来进行值的获取)
IntByReference intByReference = new IntByReference();
//调用DLL接口实例,调用其底层函数,传入一个结构体,在传入一个整数接收器
//addPrt(myStruct* ms, int* ret) 接收的参数类型为指针类型
StructTest.INSTANCE.addPrt(myStructur,intByReference);
System.out.println(intByReference.getValue());
//addRef(myStruct& ms)
System.out.println(StructTest.INSTANCE.addRef(myStructur));
//第三个小案例则不同,底层接口:int addNormal(myStruct ms) 返回一个int型的结果,
// 接收参数的类型为形参,所以使用ByValue方法进行参数传递
StructTest.myStructur.ByValue vmysStuctur = new myStructur.ByValue();
vmysStuctur.a=1;
vmysStuctur.b=3;
System.out.println(StructTest.INSTANCE.addNormal(vmysStuctur));
}
}
- 运行结果
![d0e7ecb2eab2a6e10f0f625fccb3ae0c.png](https://i-blog.csdnimg.cn/blog_migrate/0a213cacc3eda848d5506afc7612db2e.png)
结论:
1.只要涉及到结构体的传递,必须使用ByReference或者ByValue中的一种
2.指针和引用的传递使用ByReference
2.拷贝参数传递使用ByValue