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(五)

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';
        else
            s[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.1
   wVersion = MAKEWORD(1, 1); // 例:MAKEWORD(a, b) --> b | a << 8 将a左移8位变成高位与b合并起来

   // 启动
   err = WSAStartup(wVersion, &wsaData);
   if (err != 0) {
       return err;
   }
   // 检查:网络地位不等于1 || 网络高位不等于1
   if (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.1
	wVersion = MAKEWORD(1, 1);	// 例:MAKEWORD(a, b) --> b | a << 8 将a左移8位变成高位与b合并起来

	// 启动
	err = WSAStartup(wVersion, &wsaData);
	if (err != 0) {
		return err;
	}
	// 检查:网络低位不等于1 || 网络高位不等于1
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
		// 清理套接字库
		WSACleanup();
		return -1;
	}

	// 2.创建tcp套接字		// AF_INET:ipv4   AF_INET6:ipv6
	SOCKET 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>
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

anjushi_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值