c++ map底层_Java 使用JNA库调用C动态链接库

此示例讲解了Java 使用 JNA 库去调用C语言生成的动态链接库,并与spring boot 做了一个简单整合

一、创建SpringBoot工程

这里使用的开发工具为IDEA 2019版本

使用IDEA官方工具来进行SpringBoot工程的创建,这里要注意选择JDK版本,推荐使用1.8

0b54b75ff1fde400d70de52b93a2d4ac.png
创建一个spring boot项目用于后面的web整合

193e98067f7cb9443e161a98b6a6c406.png

选择web项目 选择springweb模板,点击下一步则会创建一个SpringBoot项目

d3842dab80d1b1fa4fa2c1768cd5329f.png

项目工程结构如下图所示 我删除了一些没用的东西

608a5ca1f838adb98979e70cada9e18b.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

三、创建C语言动态链接库

导入依赖项以后,首先打开VS 2019(没有2019可以使用其他版本代替)创建一个C代码动态链接库程序,如下图所示

b6f81b73db5a2a5c357518733a3d0533.png
  1. 首先解释下C语言工程中头文件与主程序的对应关系,

737450ec8b775e3bebac3018911e9623.png

C语言成功分为头文件与源文件两大块,源文件可以通俗的理解为写C逻辑函数的地方,头文件里面则是声明一些变量或者其他信息的地方,由成功目录可见有一个pch.cpp的源文件,有一个pch.h的头文件,打开pch.h头文件可以看到一些由VS自动生成的注解以及信息

9a50f1909a36480414ad6a72fd19890f.png

在头文件中写入以下代码,做一个简单的加法减法器函数

extern "C" _declspec(dllexport)int Add(int a, int b);
extern "C" _declspec(dllexport)int Sub(int a, int b);

最后在pch.cpp源文件中引入在头文件中声明的两个函数

84a2faaf5d7db7fe5dc64483549bd85b.png

然后生成一个动态链接库

bd20a1733713e5726c80c1ca53f435f5.png

==注意在生成动态链接库之前一定要选择生成的链接库的位数,假如你的java jdk是64位的这里一定要选择X64进行对应,32位的则选择x86

e1037d1c2c628405d22edbd00e4ab50b.png

生成成功后则会显示生成成功

6badb0d69c15b14519267133944e3282.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);
    }
}

下面我来解释下这段代码意思:

  1. 首先需要定义一个借口,继承LibraryStdCallLibrary,默认继承是Library如果动态链接库里的函数是以stdcall方式输出的,那么就继承StdCallLibrary,比如众所周知的kernel32库。比如上例中的接口定义:
public interface CLibrary extends Library {

}
  1. 接口内部定义

接口内部需要定义一个公共静态常量: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
  • 第二个参数是本接口的Class类型。JNA通过这个Class类型,根据指定的.dll/.so文件,动态创建接口的实例。该实例由JNA通过反射自动生成。

接口中需要定义你要用到的函数或者公共变量,不需要的可以不定义,如上例中定义的 Add,Sub 两个函数

int Add(int a,int b);
    int Sub(int a,int b);
这里要注意,在定义要使用的函数时候,函数名的大小写一定要与动态链接库中定义的函数名一致,因为它的内核是通过字符串进行反射匹配,假如大小写不一致或者说名字不一样或抛出异常,找不到该方法。
  1. 接口的调用使用
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

五、基于Web应用的整体结合

  1. 前面我们已经创建了一个基于SpringBoot的web项目,在此基础上进行项目的搭建以及完善
  2. 在项目src下创建controller,service等包
  3. 在controller与service包中进行类的创建与代码的编写
  4. 将前端静态资源文件放置在resources下的static目录中

整体工程目录如下:

382108b829eb16c52021c79fe50c05f9.png
  1. 在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

f7d4ba32aa98f8ae9b414465a12f26a0.png

这里仅测试了加法案例,减法也类似

源代码已经上传至码云

六、JNA调用C语言底层结构体进行参数赋值

  1. 整体工程结构基于上面工程,直接上代码,先上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;
}
  1. 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));
    }

}
  1. 运行结果

d0e7ecb2eab2a6e10f0f625fccb3ae0c.png

结论:

1.只要涉及到结构体的传递,必须使用ByReference或者ByValue中的一种

2.指针和引用的传递使用ByReference

2.拷贝参数传递使用ByValue

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值