乐趣区

关于java:java高级用法之JNA中的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/

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

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

退出移动版