JNA简单使用(一)(java和c++互操作)
项目中遇到需要java调用c++动态库的需求,所以就简单记录一下使用
网上查找了一下相关的资料,发现有两种通用的方式,一种是JNI(Java Native Interface)和JNA(Java Native Access),
比较了一下两者的优缺点,JNI性能比较好,但是实现起来较为复杂,JNA性能差一点,但都是封装好的工具类,使用非常方便友好,所以这边选择了JNA来实现。
这边的c++程序编译后,生成了dll文件和so文件,分别对应windows和linux,文章记录的是windows下的dll动态库调用,这边需要注意编译生成的dll文件位数,电脑操作系统是64就统一都是64位,java和c++不一致可能会出问题。
首先java需要引入pom依赖,可以查询最新的版本,https://search.maven.org/
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.11.0</version>
</dependency>
1、c++代码
c++部分代码如下,功能非常简单
a、GetMyVersion()返回一个字符串
b、PointTest(char* ret, char* source)是传入一个源字符串,反转后赋值给ret
c、根据传入的一个方法,进行java方法回调,回调了100次,并将回调次数作为参数传入java方法
#include "pch.h"
#include "Work.h"
char* GetMyVersion()
{
char* version = (char *)"c++ version 11";
return version;
}
void PointTest(char* ret, char* source)
{
int n = strlen(source);
while (0<n) {
*ret = source[n-1];
ret++;
n--;
}
}
void CallBackTest(ProgressCallback progressCallback)
{
int counter = 0;
for (; counter <= 100; counter++)
{
// do the work...
if (progressCallback)
{
// send progress update
progressCallback(counter);
}
}
}
2、java代码
对应的java相关的代码如下:
package com.example.jnatest;
import com.sun.jna.Callback;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
public interface Clibrary extends Library {
/**
* 加载c++动态库
*/
Clibrary instance = Native.load("CsharedDll", Clibrary.class);
/**
* 调用c++方法,获取C++的版本,当前方法名和c++代码中的方法名保持相同
*/
String GetMyVersion();
/**
* 测试指针的传入,把一个指针当做入参传入,c++代码对该指针的数据修改后,
* java端可以获取到修改后的数据,方法名和入参要和c++的代码保持相同
*/
void PointTest(Pointer ret, String source);
/**
* 测试回调,将一个java方法当成参数传给c++侧,c++执行完成代码逻辑后,
* 会根据传入的方法进行java方法的回调
*/
interface voidCallBack extends Callback {
void callback(int counter);
}
void CallBackTest(voidCallBack c);
}
创建一个接口去extends Library接口,目的是加载动态库,CsharedDll为c++编译成的dll或so文件名称,如下图:
低版本的jna依赖可以使用Native.loadLibrary(),但是在高版本中已经不推荐使用了,可以使用Native.load(),本例中使用后者。
里面需要注意java和c/c++数据类型对应关系,可以百度搜索下
在Clibrary中定义一个方法 ,方法和c++中的方法名称要相同,具体可以看java接口中每个方法上面的注释。
测试调用如下面代码中的注释1:
package com.example.jnatest;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JnatestApplication {
public static void main(String[] args) {
//1、java正常调用c++,并返回结果
String response = Clibrary.instance.GetMyVersion();
System.out.println(response);
//2、java侧是否可以正常解析C++返回的指针变量
String source = "hello world";
Pointer ret = new Memory(source.length());
Clibrary.instance.PointTest(ret, source);
System.out.println(new String(ret.getByteArray(0, source.length())));
//3、java触发c++回调
Clibrary.voidCallBack voidCallBack = new Clibrary.voidCallBack() {
@Override
public void callback(int counter) {
System.out.println("java counter:" + counter);
}
};
Clibrary.instance.CallBackTest(voidCallBack);
}
}
3、dll文件存放位置
百度关于dll存放java项目位置,有三种回答,第一种方式没有试过,后面的两种方式均试过,可以正常加载
一:是放到windows的对应的系统文件夹下。
二:是随便放一个位置,然后在load方法里面指定绝对文件路径,如下
Clibrary instance = Native.load("D://test//CsharedDll.dll", Clibrary.class);
三:将dll或者so文件放到java工程的resources下面,如下图
load方法里直接是相对路径,只需要指定文件名称即可,可以不需要路径和后缀名,如下:
Clibrary instance = Native.load("CsharedDll", Clibrary.class);
4、Pointer
在PointTest方法中,java代码里面第一个参数类型是Pointer,第二个参数类型是String,因为java里面参数都是值传递,但是c++里面存在引用传递和值传递(具体不太清楚,反正和java是不一样的),
所以java传递进去的参数,c++程序修改后,java如果想读取到修改后的参数,此时就需要传入Pointer类型,如果传入String,c++修改后,java是获取不到的,这里需要注意,有兴趣的可以查查其他资料。
5、JNA回调
有时候会存在c++回调java方法的需求,我自己理解,大致回调的逻辑是这样的,可能不够准确,JNA调用c++方法时,类似传入了一个java的方法引用,c++接口执行完自己的逻辑后,根据java的方法引用来回调java方法
在java中实现如下:
public interface Clibrary extends Library {
/**
* 加载c++动态库
*/
Clibrary instance = Native.load("CsharedDll", Clibrary.class);
/**
* 测试回调,将一个java方法当成参数传给c++侧,c++执行完成代码逻辑后,
* 会根据传入的方法进行java方法的回调
*/
interface voidCallBack extends Callback {
void callback(int counter);
}
void CallBackTest(voidCallBack c);
}
接口Clibrary extends Library,里面定义一个接口voidCallBack(接口名称随意)需要extends Callback,Callback里面 String METHOD_NAME = "callback",所以需要在voidCallBack中定义一个callback方法,callback方法的入参和c++保持一致就可以,这个方法里面后面实现就是回调后的具体逻辑。
然后定义一个CallBackTest方法,这个方法名要和c++代码中的回调方法名一致。
实际使用如下:
Clibrary.voidCallBack voidCallBack = new Clibrary.voidCallBack() {
@Override
public void callback(int counter) {
//实现具体的回调逻辑
System.out.println("java counter:" + counter);
}
};
Clibrary.instance.CallBackTest(voidCallBack);