Java JNA调用dll动态链接库

这篇具有很好参考价值的文章主要介绍了Java JNA调用dll动态链接库。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

记录学习内容

C编译dll环境:

        IDE:Clion

        ToolChains:MinGW64

Java环境:

        版本:jdk1.8 64位

        JNA依赖版本:5.2.0 (可根据需要升级,本文以此版本为例)

JNA(Java Native Access )提供一组Java工具类用于在运行期间动态访问系统本地库(native library:如Window的dll)而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。(概念来自百度)

先导入JNA依赖

如果是用maven就在pom文件中引入

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna</artifactId>
    <version>5.2.0</version>
</dependency>
<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna-platform</artifactId>
    <version>5.2.0</version>
</dependency>

不是maven的就下载jar包引入

一、编译动态库

编译动态库的方法参照上一篇CLion中C++加载静态库和动态库。

二、JNA调用dll——简单参数传递方法示例

CLion目录结构:

jna调用dll,java,开发语言

Math:工程目录名

cmake-build-debug-mingw:MinGW cmake编译生成的文件夹

output:工程库文件输出目录 

CMakeLists.txt:cmake配置文件 内容如下

cmake_minimum_required(VERSION 3.23)
project(Math)

set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/output) #修改所生成库的目标文件夹
set(CMAKE_CXX_STANDARD 11)

add_library(Math SHARED library.cpp)

C的代码如下:

以返回参数的平方为例

library.h:

#ifndef MATH_LIBRARY_H
#define MATH_LIBRARY_H

extern "C" double customLibTest(const char * num);

#endif //MATH_LIBRARY_H

library.cpp:

#include "library.h"

#include <iostream>
using namespace std;

double customLibTest(const char * num) {
    double x = strtod(num, nullptr);
    double res = x * x;
    cout << "输入x: " << x << "的平方是" << res << endl;
    return res;
}

将以上代码在CLion中生成dll

Java端创建测试类TestNative.java

调用customLibTest方法 传入字符串,代码如下:

package com.web.natives;

import com.sun.jna.Library;
import com.sun.jna.Native;

public class TestNative {

    public interface MathDll extends Library {

        MathDll instance = Native.load("libMath", MathDll.class);

        //简单调用dll中的方法 字符串参数 double返回值
        double customLibTest(String num);
    }


    public static void main(String[] args) {
        System.out.println("java输出返回值:" + MathDll.instance.customLibTest("5")); 
    }
}

结果:

jna调用dll,java,开发语言

 本例可见 Java端只需要创建接口interface类并继承Library,并在接口中用Native.load方法声明实例,声明接口方法,JNA框架会自动将dll与接口进行映射和解析,我们只需要调用接口的方法即可,JNA会帮我们与dll的参数和返回值的数据类型等进行匹配。

ps:上面例子中Native.load方法,第一个参数是dll库的存放路径,这里我没有给后缀.dll是因为JNA会自动给路径拼后缀,做到不同操作系统调用相同名字的库,而不需要区分到底调用的是dll还是so。

三、JNA调用dll——复杂结构体传递方法示例

有时候根据需求会传递复杂的参数,java会给C传递比较复杂的对象实体和实体的嵌套,对应的就是C的结构体和结构体嵌套。

下面以老师Teacher和学生Student为例,设定一个老师带一个班,这个班有3个学生,其中一个学生是班长。我们的目的是打印老师的信息和学生的信息。

dll中的代码如下:

library.h

#ifndef MATH_LIBRARY_H
#define MATH_LIBRARY_H

//例子1
extern "C" double customLibTest(const char * num);

//定义结构体
typedef struct {
    char *name;
    int age;
    int sex;
    long long id;
}Student;

typedef struct {
    char *name;
    Student monitor;
    Student students[3];  
}Teacher;

extern "C" void printTeacherAndStudent(Teacher *teacher);

#endif //MATH_LIBRARY_H

 library.cpp

#include "library.h"

#include <iostream>
using namespace std;

//例子1
double customLibTest(const char * num) {
    double x = strtod(num, nullptr);
    double res = x * x;
    cout << "输入x: " << x << "的平方是" << res << endl;
    return res;
}

//打印老师和学生信息
void printTeacherAndStudent(Teacher *teacher) {
    cout << "老师姓名:" << (*teacher).name << endl;
    cout << "班长姓名:" << (*teacher).monitor.name << endl;
    int size = sizeof((*teacher).student)/sizeof((*teacher).student[0]);
    for (int i = 0; i < size; ++i) {
        cout << "学生学号:" << (*teacher).student[i].id << " 学生姓名:" << (*teacher).student[i].name << endl;
    }
}

再将上面的代码生成dll

Java端代码:

首先 我们要创建与C结构体Teacher和Student相对应的Java类

Student.java:

package com.web.natives;

import com.sun.jna.Structure;

//下面的注解 声明了字段顺序,只要下面的数组中元素的顺序与C中结构体的变量顺序一致即可
@Structure.FieldOrder({"name", "age", "sex", "id"})
public class Student extends Structure {

    public String name;
    public int age;
    public int sex;
    public long id;

    //内存对齐
    public Student() {
        super(ALIGN_DEFAULT);
    }

    //实现Structure.ByValue 接口,就表示这个类代表结构体本身
    public static class ByValue extends Student implements Structure.ByValue{
        public ByValue(long id, String name, int age,int sex) {
            this.id = id;
            this.name = name;
            this.age = age;
            this.sex = sex;
        }
        public ByValue() {}
    }
}

Teacher,java

package com.web.natives;

import com.sun.jna.Structure;

@Structure.FieldOrder({"name", "monitor", "students"})
public class Teacher extends Structure {

    public String name;


    //结构体内部可以包含结构体对象的指针的数组
    //必须给students数组赋值,否则不会分配3个结构体数组的内存,导致JNA中的内存大小和C代码中结构体的内存大小不一致而调用失败。
    public Student.ByValue[] students = new Student.ByValue[3];

    //结构体内部可以嵌套结构体
    public Student.ByValue monitor;

    //内存对齐
    public Teacher() {
        super(ALIGN_DEFAULT);
    }

    //实现Structure.ByReference 接口,就表示这个类代表结构体指针
    public static class ByReference extends Teacher implements Structure.ByReference{}

}

测试类TestNative.java:

package com.web.natives;

import com.sun.jna.Library;
import com.sun.jna.Native;

public class TestNative {

    public interface MathDll extends Library {

        MathDll instance = Native.load("libMath", MathDll.class);

        //例子1 简单调用dll中的方法 字符串参数 double返回值
        double customLibTest(String num);

        //复杂的结构体传参
        void printTeacherAndStudent(Teacher.ByReference teacher);
    }


    public static void main(String[] args) {
        //创建一个Teacher结构体指针
        Teacher.ByReference teacher = new Teacher.ByReference();
        teacher.name = "王老师";
        //创建3个学生结构体
        Student.ByValue student1 = new Student.ByValue(10003101L, "张三", 18, 1);
        Student.ByValue student2 = new Student.ByValue(10003102L, "李四", 17, 0);
        Student.ByValue student3 = new Student.ByValue(10003103L, "王五", 19, 1);
        //给teacher中嵌套的结构体班长赋值
        teacher.monitor = student1;
        //给teacher中嵌套的结构体数组学生赋值
        teacher.students[0] = student1;
        teacher.students[1] = student2;
        teacher.students[2] = student3;
        //调用dll方法 传递Teacher指针给dll
        MathDll.instance.printTeacherAndStudent(teacher);
    }
}

结果:

jna调用dll,java,开发语言

上例中可见,java端只需要创建继承Structure的类,并根据具体情况实现ByValue或ByReference可以模拟C的结构体和结构体参数传递。

需要注意的是

1、结构体内存对齐,内存对齐的相关内容可查看这篇博客

结构体的内存对齐规则_利刃Cc的博客-CSDN博客_内存对齐文章来源地址https://www.toymoban.com/news/detail-521978.html

2、C与Java的结构体成员字段对应问题,字段的数据类型要对应;字段的顺序要一致,例如 C中的结构体是char *和int,那么Java声明的就应该是String和int,上例中Java的注解@Structure.FieldOrder就是为了这个规则而存在的。

3、如果C中需要的是结构体本身,那么Java端的类就实现ByValue接口;如果C中需要的是结构体指针,那么Java端的类就实现ByReference接口,上例中printTeacherAndStudent这个方法在C中的参数是指针,所以传的是Teacher的ByReference;当然,两个都实现也可以,看需要。

4、 当C中的结构体不像上例那样用Student结构体本身创建数组,而是用结构体指针数组

typedef struct {
    char *name;
    Student monitor;
    Student *students[3]; //将例子中的Student students[3]改成指针形式
}Teacher;

那么Java端用Student实现ByReference后传给C依然会报错,需要调用Structure的wirte()方法固定住Java对象的内存不让GC回收。

四、JNA调用dll——回调示例

有时根据需求,Java调用dll时,也需要有一个回调方法来达到C调用Java里的方法。

这时就引出了JNA的Callback

看例子:

library.h

#ifndef MATH_LIBRARY_H
#define MATH_LIBRARY_H

typedef void (*callback)(char *str);
extern "C" void testDllCallback(char *str, callback cb);

#endif //MATH_LIBRARY_H

library.cpp

#include "library.h"

#include <iostream>
using namespace std;

void testDllCallback(char *str, callback cb) {
    cb(str);
}

头文件定义一个有参数无返回值的callback类型和传入字符串和callback两个参数的方法

cpp文件中只需要将传入的字符串传递给callback

Java端TestNative.java如下:

package com.web.natives;

import com.sun.jna.Callback;
import com.sun.jna.Library;
import com.sun.jna.Native;

public class TestNative {

    public interface MathDll extends Library {

        MathDll instance = Native.load("libMath", MathDll.class);

        //回调用法 自定义接口继承Callback 实现Callback 调用dll的方法并将回调方法当参数传过去
        interface TestCallback extends Callback {
            void invoke(String str);
        }

        class TestCallbackImpl implements TestCallback {
            @Override
            public void invoke(String str) {
                System.out.println("dll传过来的字符串: " + str);
            }
        }

        void testDllCallback(String str, Callback cb);
    }


    public static void main(String[] args) {
        MathDll.instance.testDllCallback("我是传给C的字符串参数", new MathDll.TestCallbackImpl());
    }
}

结果:

jna调用dll,java,开发语言

 上例可见,JNA的回调,只需要创建继承Callback接口,然后实现该接口,然后传递给C即可。

五、总结

1、C的基本数据类型与Java基本数据类型对照表(来自JNA官方)

jna调用dll,java,开发语言

2、JNA调用Native时,不管是继承Library还是Structure或者Callback,方法名,变量类型、变量名、参数类型和参数名等尽量一致。

jna调用dll,java,开发语言

这个错误将是使用JNA最常见的”非法访问内存“错误,这时就要检查上面说的一致性的情况。

3、由于Java的内存分配机制导致Java对象在物理内存的地址不一定是连续的,而C对内存的处理是很严格的,因此两端交互时一定要注意这方面的问题。

Java调用原生函数时,会把传递给原生函数的Java 数据固定在内存中,这样原生函数才可以访问这些Java数据。对于没有固定住的Java对象,GC可以删除它,也可以移动它在内存中的位置,以使堆上的内存连续。如果原生函数访问没有被固定住的Java对象,就会导致调用失败。固定住哪些java对象,是JVM根据原生函数调用自动判断的。

 4、java传参都是值传递,如果想传给C并改变参数值的话,C一般都是传递参数的指针,那么Java端这面就要用到JNA的Pointer类,用法参考:

JNI便捷开发框架JNA框架之指针参数Pointer(二)_cy谭的博客-CSDN博客_jna pointer

参考播客:

开源框架JNA的使用_阿卡基YUAN的博客-CSDN博客_jna

JNI便捷开发框架JNA框架之指针参数Pointer(二)_cy谭的博客-CSDN博客_jna pointer

结构体的内存对齐规则_利刃Cc的博客-CSDN博客_内存对齐

到了这里,关于Java JNA调用dll动态链接库的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android基于JNA集成调用第三方C/C++的so库

    Android基于JNA集成调用第三方C/C++的so库 (1)引入JNA。 基于JNA开源项目,JNA对Android NDK的封装,简化Android层JNI集成调用C/C++的so库。 GitHub - java-native-access/jna: Java Native Access Java Native Access. Contribute to java-native-access/jna development by creating an account on GitHub. https://github.com/java-native-ac

    2024年02月02日
    浏览(33)
  • Python调用动态链接库(DLL)

    Python调用动态链接库(DLL) 动态链接库(Dynamic Link Libraries,简称DLL)是一种被多个程序共享的库文件,可以被用于将程序分解为小模块,减少内存占用,加快程序运行速度。而Python作为一种高级编程语言,可以方便地调用外部的DLL文件。 在Python中,可以使用ctypes模块来调用

    2024年02月13日
    浏览(37)
  • LabVIEW调用动态链接库DLL

    LabVIEW调用动态链接库DLL 要调用DLL,首先必须确定该库是C/ C ++ DLL还是Microsoft .NET Assembly / .NET DLL。 若要确定具有哪种类型的库,可以检查DLL文件的可移植可执行(PE)标头。 DLL 是 C/ C ++ DLL : 1.       如果没有头文件,请查找头文件(* .h)或函数原型定义,请跳

    2024年02月03日
    浏览(28)
  • 前端(node.js)调用dll动态链接库

    使用 js node 调用dll 动态链接库. github地址如下,包含dll,里面就一个Add方法暴露出来 github Windows 11 22H2 node v16.20.0 Python 3.11.2 需要安装这俩库 ffi-napi app.js 第一种不好使,曲线救国的方式这个是。 python-shell app.js python

    2024年02月16日
    浏览(32)
  • Rust:使用libloader调用动态链接库 (DLL)

    掘金为同人创作:掘金 最近需要使用Rust 动态调用 动态链接库,本来打算是使用 libloading 的,但是 libloading 在调用dll中的函数的时,是必须要在编译时确定参数和return的类型的。但后来发现了 libloader 这个包包, libloader 是基于 libloading 的,但是操作起来却比 libloader 方便。

    2023年04月10日
    浏览(40)
  • Unity——在C#中调用C++动态链接库(DLL)

    1、新建C++空项目 打开VS,新建一个C++空项目,自命名项目名称与位置。 2、配置项目属性为动态链接库 右键项目,点击属性,打开项目属性页,将常规中的配置类型改为动态库(.dll)。  3、添加.h头文件 右键头文件,点击添加—新建项,选择头文件.h,命名为DllForUnity.h,点击

    2024年02月10日
    浏览(32)
  • Python 使用 ctypes 调用 C/C++ DLL 动态链接库

    ctypes 有以下优点: Python内建,不需要单独安装 Python可以直接调用C/C++ 动态链接库(.dll 或 .so) 在Python一侧,不需要了解 c/c++ dll 内部的工作方式 提供了 C/C++ 数据类型与Python类型的相互映射,以及转换,包括指针类型。 ctypes 在下列场景可以发挥较大作用 运算量大的操作可以写

    2024年02月06日
    浏览(37)
  • [速成] Visual Studio C/C++创建Dll(动态链接库)并调用

    以下示例均在VS2022环境下完成。 注意: _EXPORTING 是笔者 自定义宏 ,用于区分当前是导出dll还是调用dll,要实现导出函数,还需要在实现Dll函数功能的 项目属性 里,添加 预处理定义 (记得结尾加 分号 ),如下图: _declspec(dllexport) 是VC的,表示 导出函数 到dll; _declspec(dllim

    2024年02月04日
    浏览(36)
  • VS2022环境下C++ DLL动态链接库的编写和调用

    1、新建动态链接库项目 2、新建类 3、宏定义 4、使用宏定义修饰类 调用DLL动态链接库大致有两种方法: 一种是“隐式调用”,需要.h头文件、.lib符号文件、.dll动态库文件; 一种是“显示调用”,需要.dll动态库文件; 此处介绍第一种调用方式。 1、创建项目 2、项目属性

    2024年02月14日
    浏览(36)
  • Qt6之调用Windows下vc生成的动态链接库dll

    Qt是跨平台工具,显然能和windows的动态库一起使用。 在Windows操作系统上,库以文件的形式存在,并且可以分为动态链接库(DLL) 和静态链接库两种。动态链接库文控以.dll为后缀名,静态链接库文控以.lib为后缀名。不管是动态链接库还是静态链接库,都是向它们的调用者提供变

    2024年02月09日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包