记一次JNA踩坑历程 -- JNA调用DLL

本文讲述了在Java通过JNA调用C动态库过程中遇到的一系列内存访问问题,包括类型不匹配、内存空间不连续、结构体数组初始化等,通过逐一排查并提供了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

Java通过调用C的动态库,得到数据。

结构体样式

开始测试样式

public class A extend Structure {
  public byte[] name = new byte[256];
  public Pointer value = new Memory(1024 * 64);
  public IntByference size = new IntByference ();
  
  public A(){
	super();
 }
	
  public static class ByValue extends A implements Structure.ByValue{}
  public static class ByReference extends A implements Structure.ByReference {}
  @Override
  proteced List<String> getFieldOrder(){return Arrays.asList("name","pValue","length");}
}

最终样式

public class A extend Structure {
  public byte[] name;
  public Pointer value;
  public IntByference size;
  
  public A(){
	super();
 }
	
  public static class ByValue extends A implements Structure.ByValue{}
  public static class ByReference extends A implements Structure.ByReference {}
  @Override
  proteced List<String> getFieldOrder(){return Arrays.asList("name","pValue","length");}
}

问题

1、首先是调用接口一直报错,invalid memory access
2、然后接口可以调通,值却拿不到。
3、接口调通,但是嵌入式库写数据时,报错invalid memory access
4、接口调通,可以写入数据,但只能写进去结构体数组0号位置的信息,从1号位开始就报错invalid memory access
5、获取到嵌入式的值,但是数组里面的值打印出现错误。

解决

问题一,调用接口一直报错,invalid memory access

原因是数据类型不匹配,通过Java和C的类型关系映射就解决了。
报错invalid memory access – Java调用JNA

问题二,接口可以调通,值却拿不到

通过分析嵌入式代码,发现嵌入式自己开辟了新的空间,将地址值传了出来,这样我们是获取不到值得,必须由Java这边开辟内存,然后嵌入式写进去才能获取到值。

问题三,嵌入式库写数据时,报错invalid memory access

通过让嵌入式那边打印,调试发现,结构体的指针为空,内存没有,写的时候报错。
我查看了我的写法

public class JnaService{
	public interface CLibrary extends Library{
		int test(Pointer a);
	}
}
A[] as = new A[1024];
for(int i = 0; i< a.length;i++){
	as[i] = new A();
}
Clibrary instance= Native.load(path,CLibrary.class);
instance.test(as[0].getPointer);
问题原因

创建的结构体数组,通过new初始化,内存空间不是连续的,这种情况,用int test(A[] as);,这种方式接的话,就会看到报错,structure array elements must use contiguoud memory。所以出错原因就是没创建连续空间,故这种方式不能用。

解决方法
public class JnaService{
	public interface CLibrary extends Library{
		int test(A[] as);
	}
}
A[] as = (A[])new A().toArray(1024);
Clibrary instance= Native.load(path,CLibrary.class);
instance.test(as);

这样就解决内存问题,但会引出问题4

问题四,接口调通,可以写入数据,但只能写进去结构体数组0号位置的信息,从1号位开始就报错invalid memory access

经过问题三的修复,发现,虽然有地址值,但是还是无法写入,然后通过嵌入式的打印,定位到了,只能写0索引位置的内容,其他仍然是空。这就说明,只有第一个值初始化空间了,马上整体初始化

A[] as = (A[])new A().toArray(1024);
for(A a : as){
	a.name = new byte[256];
	a.value = new Memory(1024 * 64);
	a.size = new IntByference (0);
}
Clibrary instance= Native.load(path,CLibrary.class);
instance.test(as);

然后结构体可以改为

public class A extend Structure {
  public byte[] name;
  public Pointer value;
  public IntByference size;
  
  public A(){
	super();
 }
	
  public static class ByValue extends A implements Structure.ByValue{}
  public static class ByReference extends A implements Structure.ByReference {}
  @Override
  proteced List<String> getFieldOrder(){return Arrays.asList("name","pValue","length");}
}

这样就解决传值空间没开辟的问题

问题5,获取到嵌入式的值,但是数组里面的值打印出现错误。

当获取数组中的值时候,需要用特殊的方式,因为得到的是指针,所以需要根据偏移取值,一个double是8,所以,填8*i就行,不然会报错或者获取错误数据

private List<Double> getValue(Pointer p,int len){
List<Double> result = new ArrayList<>();
	for(int i = 0;i<len;i++){
			try{
			      		result.add(pValue.getDouble(i*8))//一个Double的长度8
			     }catch(Exception e){
					    log.error(i+" 超出范围 ”+len);//需要在类上加注解@Slf4j
					    break;
			}
	}
	return result;
}

这样就能把数据解析出来了。
至此全部解决。

### 使用JNAJava Jar包中加载并调用Windows DLL函数 为了使Java应用程序能够通过JNAJava Native Access)成功加载并调用Windows上的DLL函数,需遵循一系列配置步骤。首先,确保已获取适当版本的JNA库;对于此操作,推荐使用Maven管理依赖项来简化集成过程[^4]。 #### 添加JNA依赖至项目构建工具 如果采用Maven作为项目的构建工具,则应在`pom.xml`文件中加入如下声明: ```xml <dependencies> <!-- JNA dependency --> <dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>5.2.0</version> </dependency> </dependencies> ``` 这会自动处理JNA及其所需资源的下载与路径设置工作。 #### 创建接口映射类定义目标DLL功能 接着,在Java源码里创建一个继承自`com.sun.jna.Library`的新接口,用于描述待调用的原生API签名。例如,假设要访问名为`example.dll`的一个无参返回整型值的函数`getNumber()`: ```java public interface ExampleLibrary extends Library { int getNumber(); } ``` 此处定义了一个简单的接口`ExampleLibrary`,它对应于外部C/C++编写的共享库,并指定了想要交互的具体方法原型[^2]。 #### 实现平台特定实例化逻辑 考虑到不同操作系统可能具有不同的实现细节,建议利用`Platform`辅助类判断当前运行环境再决定具体加载哪个版本的二进制文件。下面给出了一种通用做法: ```java import com.sun.jna.Native; import com.sun.jna.platform.win32.WinDef; // ... private static final String LIBRARY_NAME = System.getProperty("os.name").startsWith("Win") ? "example" : null; if (LIBRARY_NAME != null && new File(LIBRARY_NAME + ".dll").exists()) { ExampleLibrary lib = Native.load(LIBRARY_NAME, ExampleLibrary.class); } else { throw new UnsatisfiedLinkError("Failed to load library"); } ``` 上述代码片段展示了如何依据宿主机类型有条件地尝试打开指定名称的动态链接库,并将其绑定到之前建立好的抽象层上[^1]。 #### 将所有必要的支持文件打包入最终制品 最后一步是要保证生成的应用程序分发包内包含了完整的依赖关系链表。这意味着不仅要附带主程序本身,还要一并将第三方组件如JNA以及任何额外所需的本地库一同纳入其中。当使用IDE内置的功能导出可执行Jar时,请得勾选选项允许将这些附加件嵌入输出物内部结构之下[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值