示例结构,其中count
是数组中的字节数,可能为0。我想在Java中分配新实例,并读取本地分配的实例。
public class VarArray extends Structure {
public byte dummy0;
public short dummy1;
public int count;
public byte[] array;
}
在
Structure
中不允许使用array = new byte[0]
。如果计数为0,声明默认的
array = new byte [1]
将从未分配的地址读取。移除
array
字段对于读取来说是可以的,因为我可以从指针偏移Structure.size()
[编辑:不正确,取决于填充]处访问字节。但是,为了分配新实例,我需要手动确定字段大小和对齐填充,以便分配正确的内存大小。我有一个解决方案,使用两种类型——一种没有
array
用于本地分配和0计数Java分配的实例,另一种具有array
用于Java分配的1+ count实例。这似乎相当臃肿,特别是需要所需的样板代码。是否有更好的方法?
或者也许有一种简单的方法来计算字段大小和对齐方式,以便一种类型就足够了?
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
public class JnaStructTester {
/**
* For native-allocated, and 0-count JNA-allocated instances.
*/
public static class VarArray extends Structure {
public byte dummy0;
public short dummy1;
public int count;
public VarArray() {}
public VarArray(Pointer p) {
super(p);
}
public byte[] getArray() {
byte[] array = new byte[count];
if (count > 0) {
int offset = size();
getPointer().read(offset, array, 0, count);
}
return array;
}
@Override
protected List<String> getFieldOrder() {
return List.of("dummy0", "dummy1", "count");
}
}
/**
* For 1+ count JNA-allocated instances.
*/
public static class VarArrayX extends VarArray {
public byte[] array;
public VarArrayX() {}
@Override
public byte[] getArray() {
return array;
}
@Override
protected List<String> getFieldOrder() {
return List.of("dummy0", "dummy1", "count", "array");
}
}
public static void main(String[] args) {
var va0 = new VarArrayX();
va0.dummy0 = (byte) 0xef;
va0.dummy1 = (short) 0xabcd;
va0.count = 7;
va0.array = new byte[] { 1, 2, 3, 4, 5, 6, 7 };
va0.write();
var va1 = new VarArray();
va1.dummy0 = (byte) 0xab;
va1.dummy1 = (short) 0xcdef;
va1.write();
print(new Pointer(Pointer.nativeValue(va0.getPointer())));
print(new Pointer(Pointer.nativeValue(va1.getPointer())));
}
private static void print(Pointer p) {
var va = new VarArray(p);
va.read();
System.out.println(va);
System.out.println("byte[] array=" + Arrays.toString(va.getArray()));
System.out.println();
}
}
输出:
JnaStructTester$VarArray(native@0x7fb6835524b0) (8 bytes) {
byte dummy0@0=ffffffef
short dummy1@2=ffffabcd
int count@4=7
}
byte[] array=[1, 2, 3, 4, 5, 6, 7]
JnaStructTester$VarArray(native@0x7fb683551210) (8 bytes) {
byte dummy0@0=ffffffab
short dummy1@2=ffffcdef
int count@4=0
}
byte[] array=[]
(我正在使用相对较旧的JNA版本4.2.2)
更新(2020-01-07)
感谢Daniel Widdis的建议,这是一个不错的解决方案。
根据数组是否为空动态修改字段列表是不可能的。当变量数组字段未包含时,布局会被静态缓存,因此具有非空数组的将来实例会失败。改为:
- 在
ensureAllocated()
中调整数组以避免空数组错误。 - 仅在数组非空时,
writeField()
才写入字段。 readField()
从计数中设置数组大小,并在计数为0时跳过读取数组。
通过在readField()
中设置正确大小的数组,完整的数组将自动填充,无需手动创建。
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
public class JnaStructTester {
public static class VarArray extends Structure {
public short dummy0;
public int dummy1;
public byte count;
public byte[] array = new byte[0];
public VarArray() {}
public VarArray(byte[] array) {
this.count = (byte) array.length;
this.array = array;
}
public VarArray(Pointer p) {
super(p);
}
@Override
protected void ensureAllocated() {
if (count == 0) array = new byte[1];
super.ensureAllocated();
if (count == 0) array = new byte[0];
}
@Override
protected void writeField(StructField structField) {
if (structField.name.equals("array") && count == 0) return;
super.writeField(structField);
}
@Override
protected Object readField(StructField structField) {
if (structField.name.equals("array")) {
array = new byte[count];
if (count == 0) return null;
}
return super.readField(structField);
}
@Override
protected List<String> getFieldOrder() {
return List.of("dummy0", "dummy1", "count", "array");
}
}
public static void main(String[] args) {
var va0 = new VarArray(new byte[] { 1, 2, 3, 4, 5, 6, 7 });
va0.dummy0 = 0x4321;
va0.dummy1 = 0xabcdef;
va0.write();
var va1 = new VarArray();
va1.dummy0 = 0x4321;
va1.dummy1 = 0xabcdef;
va1.write();
print(new Pointer(Pointer.nativeValue(va0.getPointer())));
print(new Pointer(Pointer.nativeValue(va1.getPointer())));
}
private static void print(Pointer p) {
var va = new VarArray(p);
va.read();
System.out.println(va);
System.out.println("byte[] array=" + Arrays.toString(va.array));
System.out.println();
}
}
输出:
JnaStructTester$VarArray(native@0x7fd85cf1ffb0) (12 bytes) {
short dummy0@0=4321
int dummy1@4=abcdef
byte count@8=7
byte array[7]@9=[B@4f2410ac
}
byte[] array=[1, 2, 3, 4, 5, 6, 7]
JnaStructTester$VarArray(native@0x7fd85cf20690) (12 bytes) {
short dummy0@0=4321
int dummy1@4=abcdef
byte count@8=0
byte array[0]@9=[B@722c41f4
}
byte[] array=[]
我只在有限的使用场景中测试过这个功能,如果在Structure
上调用其他方法,它可能无法正常工作。
ensureAllocated
/readField
/writeField
进行重写更新了解决方案。 - boot-and-bonnet