简介

后面咱们讲到了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/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

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