Java使用JNA调用C++动态链接库——JNA-JNI(二)

2023-10-09 17:30

本文主要是介绍Java使用JNA调用C++动态链接库——JNA-JNI(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Java使用JNA调用C++动态链接库——JNA-JNI(二)

系列文章:
Java通过JNI调用C++动态链接库dll,并打在jar包内 ——JNA-JNI(一)
Java使用JNA调用C++动态链接库——JNA-JNI(二)
Mac M1 Xcode创建动态链接库dylib(c++)——JNA-JNI(三)
JNA调用dll(c++)附带解析xml——JNA-JNI(四)
JNA参数类型转换(含接收、发送结构体)——JNA-JNI(五)

本文目录

    • Java使用JNA调用C++动态链接库——JNA-JNI(二)
      • JNA介绍
      • 生成动态链接库
      • 使用
        • maven引入
        • interface
        • jar包解析
        • 调用
        • 调试台输出
        • udp服务作为动态链接库
      • mac m1下问题

JNA介绍

JNA(Java Native Access):一个开源的Java框架,是Sun公司推出的一种调用本地方法的技术,是建立在经典的JNI基础之上的一个框架。之所以说它是JNI的替代者,是因为JNA大大简化了调用本地方法的过程,使用很方便,基本上不需要脱离Java环境就可以完成。

JNA调用C/C++的过程大致如下:

不需要重写我们的动态链接库文件,而是有直接调用的API,大大简化了工作量

  • 注意:

JNA是建立在JNI技术基础之上的一个Java类库,它使您可以方便地使用java直接访问动态链接库中的函数。
原来使用JNI,你必须手工用C写一个动态链接库,在C语言中映射Java的数据类型。
JNA中,它提供了一个动态的C语言编写的转发器,可以自动实现Java和C的数据类型映射,你不再需要编写C动态链接库。
也许这也意味着,使用JNA技术比使用JNI技术调用动态链接库会有些微的性能损失。但总体影响不大,因为JNA也避免了JNI的一些平台配置的开销。

生成动态链接库

在windows下生成.dll,在linux下生成.so文件,在macos下生成.dylib

使用JNA,动态链接库一般是第三方的,不允许修改,我们自定义生成一个(正式开发中一般是某产品成型的链接库,只需要知道接口函数即可)

simple.h

#ifndef SIMPLE_H_INCLUDED
#define SIMPLE_H_INCLUDED#ifdef __cplusplus
#define EXPORT extern "C" __declspec (dllexport)
#else
#define EXPORT __declspec (dllexport)
#endif // __cplusplus#include <windows.h>EXPORT  int pow2(int a);
EXPORT  void upstr(char *str, char *s);#endif // SIMPLE_H_INCLUDED

simple.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<iostream>
#include "simple.h"using namespace std;int pow2(int a)
{return a*a;
}void upstr(char *str, char *s)
{int i;for(i = 0; i < strlen(str); i++){if(str[i] >= 'a' && str[i] <= 'z')s[i] = str[i] - 'a' + 'A';elses[i] = str[i];}s[i] = '\0';cout << "str = " << str << endl;cout << "s = " << str << endl;
}

使用上一篇jni教程中生成动态链接库的方法,生成Project.dll

使用

maven引入
<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
<dependency><groupId>net.java.dev.jna</groupId><artifactId>jna</artifactId><version>5.2.0</version>
</dependency>
interface
    public interface CLibrary extends Library {DemoApplication.CLibrary INSTANCE = (DemoApplication.CLibrary) Native.loadLibrary(loadNative("Project6"), DemoApplication.CLibrary.class); // 引入库文件public int pow2(int i);public void upstr(String str, String s);}
jar包解析
    private synchronized static String loadNative(String nativeName) {String systemType = System.getProperty("os.name");String fileExt = (systemType.toLowerCase().indexOf("win") != -1) ? ".dll" : ".so";File path = new File(".");//将所有动态链接库dll/so文件都放在一个临时文件夹下,然后进行加载//这是应为项目为可执行jar文件的时候不能很方便的扫描里面文件//此目录放置在与项目同目录下的natives文件夹下String sysUserTempDir = path.getAbsoluteFile().getParent() + File.separator + "natives";//        System.out.println("------>>native lib临时存放目录 : " + sysUserTempDir);String fileName = nativeName + fileExt;InputStream in = null;BufferedInputStream reader = null;FileOutputStream writer = null;File tempFile = new File(sysUserTempDir + File.separator + fileName);if (!tempFile.getParentFile().exists())tempFile.getParentFile().mkdirs();if (tempFile.exists()) {tempFile.delete();}try {//读取文件形成输入流in = DemoApplication.class.getResourceAsStream("/native/" + fileName);if (in == null)in = DemoApplication.class.getResourceAsStream("native/" + fileName);DemoApplication.class.getResource(fileName);reader = new BufferedInputStream(in);writer = new FileOutputStream(tempFile);byte[] buffer = new byte[1024];while (reader.read(buffer) > 0) {writer.write(buffer);buffer = new byte[1024];}} catch (IOException e) {e.printStackTrace();} finally {try {if (in != null)in.close();if (writer != null)writer.close();} catch (IOException e) {e.printStackTrace();}}
//        System.load(tempFile.getPath());
//        System.out.println("------>> 加载native文件 :" + tempFile.getPath() + "成功!!");return tempFile.getPath();}
调用
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {...int res = DemoApplication.CLibrary.INSTANCE.pow2(2);System.out.println(res);DemoApplication.CLibrary.INSTANCE.upstr("string1", "string2");}
}
调试台输出
4
str = string1
s = STRING1
udp服务作为动态链接库

客户端 C++

#include <iostream>
#include <WinSock2.h>
#include <Ws2tcpip.h>#pragma comment(lib, "ws2_32.lib")
#pragma warning(disable:4996)int main(void) {// 1.初始化套接字库WORD wVersion;WSADATA wsaData;int err;// 可以理解为1.1wVersion = MAKEWORD(1, 1); // 例:MAKEWORD(a, b) --> b | a << 8 将a左移8位变成高位与b合并起来// 启动err = WSAStartup(wVersion, &wsaData);if (err != 0) {return err;}// 检查:网络地位不等于1 || 网络高位不等于1if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {// 清理套接字库WSACleanup();return -1;}// 创建TCP套接字SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);SOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 服务器地址addrSrv.sin_port = htons(6000);  // 端口号addrSrv.sin_family = AF_INET;  // 地址类型(ipv4)// 2.连接服务器int err_log = connect(sockCli, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));if (err_log == 0) {printf("连接服务器成功!\n");}else {printf("连接服务器失败!\n");return -1;}char recvBuf[100];char sendBuf[] = "你好,服务器,我是客户端!";// 3.发送数据到服务器send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);// 4.接收服务器的数据recv(sockCli, recvBuf, sizeof(recvBuf), 0);std::cout << recvBuf << std::endl;// 5.关闭套接字并清除套接字库closesocket(sockCli);WSACleanup();system("pause");return 0;
}

服务器端 C++

#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <stdio.h>
#include <WinSock2.h>
#include <windows.h> 
#include "udp.h"#pragma comment(lib, "ws2_32.lib")
#pragma warning(disable:4996)using namespace std;int udpserver()
{// 1.初始化套接字库WORD wVersion;WSADATA wsaData;int err;// 设置版本,可以理解为1.1wVersion = MAKEWORD(1, 1);	// 例:MAKEWORD(a, b) --> b | a << 8 将a左移8位变成高位与b合并起来// 启动err = WSAStartup(wVersion, &wsaData);if (err != 0) {return err;}// 检查:网络低位不等于1 || 网络高位不等于1if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {// 清理套接字库WSACleanup();return -1;}// 2.创建tcp套接字		// AF_INET:ipv4   AF_INET6:ipv6SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);// 准备绑定信息SOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);	// 设置绑定网卡addrSrv.sin_family = AF_INET;		// 设置绑定网络模式addrSrv.sin_port = htons(6000);		// 设置绑定端口// hton: host to network  x86:小端    网络传输:htons大端// 3.绑定到本机int retVal = bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));if (retVal == SOCKET_ERROR) {printf("Failed bind:%d\n", WSAGetLastError());return -1;}// 4.监听,同时能接收10个链接if (listen(sockSrv, 10) == SOCKET_ERROR) {printf("Listen failed:%d", WSAGetLastError());return -1;}std::cout << "Server start at port: 6000" << std::endl;SOCKADDR_IN addrCli;int len = sizeof(SOCKADDR);char recvBuf[100];char sendBuf[100];while (1) {// 5.接收连接请求,返回针对客户端的套接字SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrCli, &len);if (sockConn == SOCKET_ERROR) {//printf("Accept failed:%d", WSAGetLastError());std::cout << "Accept failed: " << WSAGetLastError() << std::endl;break;}//printf("Accept client IP:[%s]\n", inet_ntoa(addrCli.sin_addr));std::cout << "Accept client IP: " << inet_ntoa(addrCli.sin_addr) << std::endl;// 6.发送数据sprintf_s(sendBuf, "hello client!\n");int iSend = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);if (iSend == SOCKET_ERROR) {std::cout << "send failed!\n";break;}// 7.接收数据recv(sockConn, recvBuf, 100, 0);std::cout << recvBuf << std::endl;// 关闭套接字closesocket(sockConn);}// 8.关闭套接字closesocket(sockSrv);// 9.清理套接字库WSACleanup();return 0;}

java调用(jar包调用部分不变,只修改interface和调用)

    public interface CLibrary extends Library {// 通过INSTANCE这个常量,就可以获得这个接口的实例DemoApplication.CLibrary INSTANCE = (DemoApplication.CLibrary) Native.loadLibrary(loadNative("Project6"), DemoApplication.CLibrary.class); // 引入库文件public int udpserver();}public static void main(String[] args) {SpringApplication.run(DemoApplication.class);DemoApplication.CLibrary.INSTANCE.udpserver();}

结果如下

客户端显示连接

服务器端显示接收消息

mac m1下问题

本地java版本

xx@xx ~ % java -version
openjdk version "1.8.0_282"
OpenJDK Runtime Environment (Zulu xx-aarch64) (build 1.8.0_282-xx)

问题:java.lang.UnsatisfiedLinkError

Caused by: java.lang.UnsatisfiedLinkError: ...: tried: '....tmp' (fat file, but missing compatible architecture (have 'i386,x86_64', need 'arm64e')), '/usr/lib/jna6476532391668202562.tmp' (no such file)at java.lang.ClassLoader$NativeLibrary.load(Native Method)at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1950)at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1832)at java.lang.Runtime.load0(Runtime.java:811)at java.lang.System.load(System.java:1088)at com.sun.jna.Native.loadNativeDispatchLibraryFromClasspath(Native.java:1018)at com.sun.jna.Native.loadNativeDispatchLibrary(Native.java:988)at com.sun.jna.Native.<clinit>(Native.java:195)at co.elastic.apm.attach.bytebuddy.agent.VirtualMachine$ForHotSpot$Connection$ForJnaPosixSocket$Factory.withDefaultTemporaryFolder(VirtualMachine.java:893)at co.elastic.apm.attach.bytebuddy.agent.VirtualMachine$ForHotSpot.attach(VirtualMachine.java:243)
... 11 more

解决:jna 5.7 版本以及更新的版本中,才有针对 arm 架构进行适配

        <dependency><groupId>net.java.dev.jna</groupId><artifactId>jna</artifactId><version>5.9.0</version></dependency>

这篇关于Java使用JNA调用C++动态链接库——JNA-JNI(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/174424

相关文章

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

使用Python构建一个Hexo博客发布工具

《使用Python构建一个Hexo博客发布工具》虽然Hexo的命令行工具非常强大,但对于日常的博客撰写和发布过程,我总觉得缺少一个直观的图形界面来简化操作,下面我们就来看看如何使用Python构建一个... 目录引言Hexo博客系统简介设计需求技术选择代码实现主框架界面设计核心功能实现1. 发布文章2. 加

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt

SpringBoot集成Milvus实现数据增删改查功能

《SpringBoot集成Milvus实现数据增删改查功能》milvus支持的语言比较多,支持python,Java,Go,node等开发语言,本文主要介绍如何使用Java语言,采用springboo... 目录1、Milvus基本概念2、添加maven依赖3、配置yml文件4、创建MilvusClient

浅析Java中如何优雅地处理null值

《浅析Java中如何优雅地处理null值》这篇文章主要为大家详细介绍了如何结合Lambda表达式和Optional,让Java更优雅地处理null值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录场景 1:不为 null 则执行场景 2:不为 null 则返回,为 null 则返回特定值或抛出异常场景

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

SpringMVC获取请求参数的方法

《SpringMVC获取请求参数的方法》:本文主要介绍SpringMVC获取请求参数的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下... 目录1、通过ServletAPI获取2、通过控制器方法的形参获取请求参数3、@RequestParam4、@

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

SpringBoot应用中出现的Full GC问题的场景与解决

《SpringBoot应用中出现的FullGC问题的场景与解决》这篇文章主要为大家详细介绍了SpringBoot应用中出现的FullGC问题的场景与解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录Full GC的原理与触发条件原理触发条件对Spring Boot应用的影响示例代码优化建议结论F