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 provides two interfaces, ByValue and ByReference:

 public abstract class Structure {

    public interface ByValue { }

    public interface ByReference { }

To use it, you need to inherit the corresponding interface.

Special type of Structure

In addition to the pass-by-value or pass-by-reference structs we mentioned above, there are other more complex struct usages.

array of structs as parameters

First, let's take a look at the case of a structure array as a parameter:

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

For the corresponding structure array, you can directly use the corresponding Structure array in JNA to map:

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

Array of structs as return value

If the native method returns a pointer to a structure, which is essentially an array of structures, what should we do?

First look at the definition of the native method:

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

The get_displays method returns a pointer to an array of structures, and pcount is the number of structures.

The corresponding JAVA code is as follows:

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

For the first method, we only return a Display, but it can be converted into a structure array by the Structure.toArray(int) method. Passed into the second method, the specific invocation method is as follows:

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

structure within structure

The structure can also be embedded in the structure, first look at the definition of the native method:

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

typedef struct _Line {
  Point start;
  Point end;
} Line;

The corresponding JAVA code is as follows:

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

class Line extends Structure {
  public Point start;
  public Point end;
}

If it is a pointer to a struct in the following struct:

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

Then the corresponding code is as follows:

 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;
}

Or directly use Pointer as the property value of Structure:

 class Line2 extends Structure {
  public Pointer p1;
  public Pointer p2;
}

Line2 line2;
Point p1, p2;
...
line2.p1 = p1.getPointer();
line2.p2 = p2.getPointer();

array in structure

If the struct has a fixed-size array:

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

Then we need to specify the size of the data in JAVA:

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

If the structure is a dynamically sized array:

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

Then we need to define a constructor in the JAVA structure, pass in the size of bufferSize, and allocate the corresponding memory space:

 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();
  }
}

Variable fields in structs

By default, the contents of the structure are the same as the contents of native memory. JNA will write the contents of the Structure to the native memory before the function call, and write the contents of the native memory back to the Structure after the function call.

The default is to write and write all fields in the struct. But in some cases we want some fields not to be updated automatically. At this time, the volatile keyword can be used, as follows:

 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 included in http://www.flydean.com/08-jna-structure/

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!


flydean
890 声望433 粉丝

欢迎访问我的个人网站:www.flydean.com