Javaの高度な使用法:JNAの構造

一緒に書く習慣を身につけましょう!「ナゲッツデイリーニュープラン・4月アップデートチャレンジ」に参加して11日目です。クリックしてイベントの詳細をご覧ください

序章

以前、JAVAコードとJNAのネイティブコード間のマッピングについて説明しました。TypeMapperを使用してJAVAの型とネイティブの型をマッピングできますが、ネイティブのデータ型はすべて基本型です。ネイティブのデータ型がマッピング方法の場合複雑な構造型?

恐れることはありません。JNAはこれらのマッピングを支援するStructureクラスを提供しています。

ネイティブで構造体

構造体はいつ使用されますか?一般に、データクラスをカスタマイズする必要がある場合、一般に、JAVAでクラスを定義する必要があります(JDK17では、より単純なレコードを使用して置き換えることができます)が、データ構造のクラスを定義することは明らかにです少し肥大化しているため、ネイティブ言語では、構造体と呼ばれるより単純なデータ構造がいくつかあります。

まず、構造体の定義を見てみましょう。

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

上記のコードでは、Pointer structデータクラスを定義します。ここで、intのx値とy値は、Pointの水平座標と垂直座標を表すように定義されています。

構造体の使用には2つの状況があります。1つは値による受け渡しであり、もう1つは参照による受け渡しです。まず、これら2つの状況がネイティブメソッドでどのように使用されるかを見てみましょう。

参照渡し:

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

値渡し:

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

構造

では、ネイティブメソッドでstructデータ型の使用法をマッピングする方法は?JNAはStructureクラスを提供します。

デフォルトでは、Structureがパラメーターまたは戻り値として使用されている場合、マッピングはstruct *であり、Structure内のフィールドを表す場合、マッピングはstructです。

もちろん、Structure.ByReferenceまたはStructure.ByValueを強制的に使用して、参照渡しするか値渡しするかを指定することもできます。

上記のネイティブの例、JNAの構造を使用してマッピングを実装する方法を見てみましょう。

ポインタマップ:

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メソッドを呼び出して、ネイティブメモリから対応するコンテンツを読み取り、構造内の対応する値を上書きすることができます。

JAVAでfinalフィールドを使用する方法を見てみましょう。

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

すべてのフィールドの初期化は、コンストラクターまたは静的メソッドブロックで実行する必要があることに注意してください。

要約する

構造はネイティブメソッドでよく使用されるデータ型であり、JNAでそれをマッピングする方法は私たちが習得する必要があるものです。

この記事はwww.flydean.com/08-jna-struに掲載されています…

最も人気のある解釈、最も深遠な乾物、最も簡潔なチュートリアル、そしてあなたが知らない多くのトリックがあなたが発見するのを待っています!

私の公式アカウントに注意を払うことを歓迎します:「それらをプログラムする」、テクノロジーを理解し、あなたをよりよく理解する!

おすすめ

転載: juejin.im/post/7085226231263657992