Java advanced usage: Structure in JNA

Get into the habit of writing together! This is the 11th day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .

Introduction

Earlier we talked about the mapping between JAVA code and native code in JNA. Although TypeMapper can be used to map types in JAVA and types in native, the data types in native are all basic types. If the data type in native is How to map complex struct types?

Don't be afraid, JNA provides the Structure class to help us with these mappings.

struct in native

When will struct be used? In general, when we need to customize a data class, in general, we need to define a class in JAVA (in JDK17, a simpler record can be used to replace it), but defining a class for a data structure is obviously It's a bit bloated, so in native languages, there are some simpler data structures called structs.

Let's first look at the definition of a struct:

typedef struct _Point {
  int x, y;
} Point;
复制代码

In the above code, we define a Pointer struct data class, in which the x and y values ​​of int are defined to represent the horizontal and vertical coordinates of Point.

There are two situations in the use of struct, one is pass-by-value and the other is pass-by-reference. Let's first look at how these two situations are used in the native method:

Pass by reference:

Point* translate(Point* pt, int dx, int dy);
复制代码

Pass by value:

Point translate(Point pt, int dx, int dy);
复制代码

Structure

So how to map the usage of the struct data type in the native method? JNA provides us with the Structure class.

By default, if Structure is used as a parameter or return value, then the mapping is struct*, if it represents a field in the Structure, then the mapping is struct.

Of course, you can also force the use of Structure.ByReference or Structure.ByValue to indicate whether to pass by reference or pass by value.

Let's take a look at the above native example, how to implement the mapping using JNA's Structure:

pointer map:

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);
复制代码

Pass-by-value mapping:

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();
复制代码

Of course, you can also force the use of Structure.writeField(String) to write field information into memory, or use Structure.read() to update the information of the entire structure or use data.readField("refCount") to update specific fields information.

read-only fields in structs

If you do not want to modify the content of Structure from the JAVA code, you can mark the corresponding field as final. In this case, although the JAVA code cannot directly modify it, the read method can still be called to read the corresponding content from the native memory and overwrite the corresponding value in the Structure.

Let's see how to use final fields in JAVA:

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

Note that all field initialization should be done in the constructor or static method block.

Summarize

Structure is a data type that is often used in native methods, and the method of mapping it in JNA is what we need to master.

This article has been published on www.flydean.com/08-jna-stru…

The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!

Guess you like

Origin juejin.im/post/7085226231263657992