文章目录

  • 简介
  • native中的struct
  • Structure
  • 特殊类型的Structure
    • 结构体数组作为参数
    • 结构体数组作为返回值
    • 结构体中的结构体
    • 结构体中的数组
    • 结构体中的可变字段
    • 结构体中的只读字段
  • 总结

简介

前面我们讲到了JNA中JAVA代码和native代码的映射,虽然可以通过TypeMapper来将JAVA中的类型和native中的类型进行映射,但是native中的数据类型都是基础类型,如果native中的数据类型是复杂的struct类型该如何进行映射呢?

不用怕,JNA提供了Structure类,来帮助我们进行这些映射处理。

native中的struct

什么时候会用到struct呢?一般情况下,当我们需要自定义一个数据类的时候,一般情况下,在JAVA中需要定义一个class(在JDK17中,可以使用更加简单的record来进行替换),但是为一个数据结构定义class显然有些臃肿,所以在native语言中,有一些更简单的数据结构叫做struct。

我们先看一个struct的定义:

typedef struct _Point {int x, y;
} Point;

上面的代码中,我们定义了一个Pointer的struct数据类下,在其中定义了int的x和y值表示Point的横纵坐标。

struct的使用有两种情况,一种是值传递,一种是引用传递。先来看下这两种情况在native方法中是怎么使用的:

引用传递:

Point* translate(Point* pt, int dx, int dy);

值传递:

Point translate(Point pt, int dx, int dy);

Structure

那么对于native方法中的struct数据类型的使用方式,应该如何进行映射呢? JNA为我们提供了Structure类。

默认情况下如果Structure是作为参数或者返回值,那么映射的是struct*,如果表示的是Structure中的一个字段,那么映射的是struct。

当然你也可以强制使用Structure.ByReference 或者 Structure.ByValue 来表示是传递引用还是传值。

我们看下上面的native的例子中,如果使用JNA的Structure来进行映射应该怎么实现:

指针映射:

class Point extends Structure { public int x, y; }
Point translate(Point pt, int x, int y);
...
Point pt = new Point();
Point result = translate(pt, 100, 100);

传值映射:

class Point extends Structure {public static class ByValue extends Point implements Structure.ByValue { }public int x, y;
}
Point.ByValue translate(Point.ByValue pt, int x, int y);
...
Point.ByValue pt = new Point.ByValue();
Point result = translate(pt, 100, 100);

Structure内部提供了两个interface,分别是ByValue和ByReference:

public abstract class Structure {public interface ByValue { }public interface ByReference { }

要使用的话,需要继承对应的interface。

特殊类型的Structure

除了上面我们提到的传值或者传引用的struct,还有其他更加复杂的struct用法。

结构体数组作为参数

首先来看一下结构体数组作为参数的情况:

void get_devices(struct Device[], int size);

对应结构体数组,可以直接使用JNA中对应的Structure数组来进行映射:

int size = ...
Device[] devices = new Device[size];
lib.get_devices(devices, devices.length);

结构体数组作为返回值

如果native方法返回的是一个指向结构体的指针,其本质上是一个结构体数组,我们应该怎么处理呢?

先看一下native方法的定义:

struct Display* get_displays(int* pcount);
void free_displays(struct Display* displays);

get_displays方法返回的是一个指向结构体数组的指针,pcount是结构体的个数。

对应的JAVA代码如下:

Display get_displays(IntByReference pcount);
void free_displays(Display[] displays);

对于第一个方法来说,我们只返回了一个Display,但是可以通过Structure.toArray(int) 方法将其转换成为结构体数组。传入到第二个方法中,具体的调用方式如下:

IntByReference pcount = new IntByReference();
Display d = lib.get_displays(pcount);
Display[] displays = (Display[])d.toArray(pcount.getValue());
...
lib.free_displays(displays);

结构体中的结构体

结构体中也可以嵌入结构体,先看下native方法的定义:

typedef struct _Point {int x, y;
} Point;typedef struct _Line {Point start;Point end;
} Line;

对应的JAVA代码如下:

class Point extends Structure {public int x, y;
}class Line extends Structure {public Point start;public Point end;
}

如果是下面的结构体中的指向结构体的指针:

typedef struct _Line2 {Point* p1;Point* p2;
} Line2;

那么对应的代码如下:

class Point extends Structure {public static class ByReference extends Point implements Structure.ByReference { }public int x, y;
}
class Line2 extends Structure {public Point.ByReference p1;public Point.ByReference p2;
}

或者直接使用Pointer作为Structure的属性值:

class Line2 extends Structure {public Pointer p1;public Pointer p2;
}Line2 line2;
Point p1, p2;
...
line2.p1 = p1.getPointer();
line2.p2 = p2.getPointer();

结构体中的数组

如果结构体中带有固定大小的数组:

typedef struct _Buffer {char buf1[32];char buf2[1024];
} Buffer;

那么我们在JAVA中需要指定数据的大小:

class Buffer extends Structure {public byte[] buf1 = new byte[32];public byte[] buf2 = new byte[1024];
}

如果结构体中是动态大小的数组:

typedef struct _Header {int flags;int buf_length;char buffer[1];
} Header;

那么我们需要在JAVA的结构体中定义一个构造函数,传入bufferSize的大小,并分配对应的内存空间:

class Header extends Structure {public int flags;public int buf_length;public byte[] buffer;public Header(int bufferSize) {buffer = new byte[bufferSize];buf_length = buffer.length;allocateMemory();}
}

结构体中的可变字段

默认情况下结构体中的内容和native memory的内容是一致的。JNA会在函数调用之前将Structure的内容写入到native memory中,并且在函数调用之后,将 native memory中的内容回写到Structure中。

默认情况下是将结构体中的所有字段都进行写入和写出。但是在某些情况下,我们希望某些字段不进行自动更新。这个时候就可以使用volatile关键字,如下所示:

class Data extends com.sun.jna.Structure {public volatile int refCount;public int value;
}
...
Data data = new Data();

当然,你也可以强制使用Structure.writeField(String)来将字段信息写入内存中,或者使用Structure.read() 来更新整个结构体的信息或者使用data.readField(“refCount”)来更新具体字段信息。

结构体中的只读字段

如果不想从JAVA代码中对Structure的内容进行修改,则可以将对应的字段标记为final。在这种情况下,虽然JAVA代码不能直接对其进行修改,但是仍然可以调用read方法从native memory中读取对应的内容并覆盖Structure中对应的值。

来看下JAVA中如何使用final字段:

class ReadOnly extends com.sun.jna.Structure {public final int refCount;{// 初始化refCount = -1;// 从内存中读取数据read();}
}

注意所有的字段的初始化都应该在构造函数或者静态方法块中进行。

总结

结构体是native方法中经常会使用到的一种数据类型,JNA中对其进行映射的方法是我们要掌握的。

本文已收录于 http://www.flydean.com/08-jna-structure/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

java高级用法之:JNA中的Structure相关推荐

  1. java高级用法之:JNA类型映射应该注意的问题

    文章目录 简介 String Buffers,Memory,数组和Pointer 可变参数 总结 简介 JNA提供JAVA类型和native类型的映射关系,但是这一种映射关系只是一个大概的映射,我们在 ...

  2. java高级用法之:在JNA中使用类型映射

    文章目录 简介 类型映射的本质 TypeMapper NativeMapped 总结 简介 JNA中有很多种映射,library的映射,函数的映射还有函数参数和返回值的映射,libary和函数的映射比 ...

  3. java高级用法之:调用本地方法的利器JNA

    文章目录 简介 JNA初探 JNA加载native lib的流程 本地方法中的结构体参数 总结 简介 JAVA是可以调用本地方法的,官方提供的调用方式叫做JNI,全称叫做java native int ...

  4. java高级用法之:无所不能的java,本地方法调用实况

    文章目录 简介 JDK的本地方法 自定义native方法 总结 简介 相信每个程序员都有一个成为C++大师的梦想,毕竟C++程序员处于程序员鄙视链的顶端,他可以俯视任何其他语言的程序员. 但事实情况是 ...

  5. java optional 用法_Java 8中的Optional: 如何正确使用?

    Java 8中出现一个新的Optional类型,和其他语言中null的替代品类似. 本文将讨论如何使用这种新类型,即它的主要用例是什么. 什么是Optional类型? Optional是对单个对象包装 ...

  6. Java高级语法笔记-异常中finally的使用

    finally void test() throws Exception { try{ } finally{ // 此段代码总是执行, 用于退出前的清理工作 } } 无论try中有什么发生,final ...

  7. 【JAVA高级】——吃透JDBC中的SQL注入问题和解决方案

    ✅作者简介:热爱国学的Java后端开发者,修心和技术同步精进.

  8. 关于python中def的高级用法,def中套def,python函数装饰器

    Python函数装饰器 装饰器(Decorators)是 Python 的一个重要部分.简单地说:他们是修改其他函数的功能的函数.他们有助于让我们的代码更简短,也更Pythonic(Python范儿) ...

  9. Python中and和or运算符的高级用法

    在 Python 中,and 和 or 都是布尔运算符,用于比较两个或多个表达式的真假值.它们的行为有一些类似于逻辑运算符,但是有一些重要的区别. 基础用法 and 运算符用于比较两个表达式的真假值, ...

最新文章

  1. LeetCode简单题之截断句子
  2. C++ 标准库中的异常
  3. 用TextKit实现表情混排
  4. 【MPI编程】任意数节点的树形求和(高性能计算)
  5. 解析:IEEE批准首个联邦机器学习框架标准
  6. 关于CVE-2019-0708 - 数组越界
  7. VSCode设置命令行终端为Git
  8. Yii2 理解Validator
  9. ios即时通讯客户端开发之-mac上搭建openfire服务器
  10. Fedora25安装mariadb并设置权限
  11. Linux 下MongoDb的安装
  12. 实验报告:统计字符串中子字符串出现的次数
  13. FPGA信号处理系列文章——相关与卷积
  14. linux安装tailf 命令
  15. win10系统迁移后系统重装_win10系统迁移【搞定手段】
  16. 各种常用的默认端口号
  17. SSH、SSL、TSL
  18. c语言中sub是什么指令,汇编 – SUB指令的目的是什么?
  19. proftpd mysql_虚拟主机与Proftpd和MySQL(包括配额)在Ubuntu 8.04 LTS
  20. 人所共有的19个不良习惯

热门文章

  1. 【JAVA学习第三期】克隆的几种方式
  2. unity 应用商店下载的插件位置
  3. 哈希(Hash)的详细介绍
  4. fastnest怎么一键排版_一个简单的宏实现一键排版(整理复盘)
  5. 中小河流雨水情监测_水文监测预警系统
  6. linux强制卸载光盘,umount强制卸载不起作用,卸载光驱终极办法---fuser
  7. mysql查询某字段的重复数据
  8. PHP 开发 APP 接口 学习笔记与总结 - 静态缓存
  9. 直通车如何快速涨质量分到10分【全平台erp、进销存软件】
  10. java模拟病毒传染_模拟细菌(病毒)传播(java作业)