Пример структуры, где count
— количество байтов в массиве, которое может быть равно 0. Я хочу выделить новые экземпляры в Java, а также прочитать экземпляры, выделенные в собственном коде.
public class VarArray extends Structure {
public byte dummy0;
public short dummy1;
public int count;
public byte[] array;
}
Использование array = new byte[0]
запрещено в Structure
.
Объявление по умолчанию array = new byte[1]
будет читать с нераспределенного адреса, если счетчик равен 0.
Удаление поля array
подходит для чтения, так как я могу получить доступ к байтам по смещению указателя Structure.size()
[Редактировать: неверно, зависит от заполнения]. Однако для выделения нового экземпляра мне нужно вручную определить размеры полей и заполнение выравнивания, чтобы выделить правильный размер памяти.
У меня есть решение, использующее два типа: один без array
для собственных экземпляров и экземпляров, выделенных для Java, с нулевым счетом, и подтип с array
для экземпляров, выделенных Java для 1+ экземпляров. Это кажется довольно раздутым, особенно с обязательным стандартным кодом.
Есть ли способ лучше?
Или, может быть, простой способ рассчитать размер поля и выравнивание, чтобы одного типа было достаточно?
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)
Благодаря предложениям Дэниела Виддиса, вот хорошее решение.
Динамическое изменение списка полей в зависимости от того, пуст ли массив, невозможно. Макет статически кэшируется, когда поле массива переменных не включено, поэтому в будущем экземпляр с непустым массивом потерпит неудачу. Вместо:
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
.
Я действительно думаю, что решение с двумя структурами, с версией массива, расширяющей другую, но просто добавляющей новое поле, является разумным решением. Ваша озабоченность по поводу «раздувания шаблона» значительно снижается с JNA 5.X, который имеет аннотацию @FieldOrder
, которая значительно уменьшает этот шаблон. Ваши структуры будут довольно простыми:
@FieldOrder({"dummy0", "dummy1", "count"})
public class VarArray extends Structure {
public byte dummy0;
public short dummy1;
public int count;
}
@FieldOrder({"dummy0", "dummy1", "count", "array"})
public class VarArrayX extends VarArray {
public byte[] array = new byte[1];
public VarArrayX(int arraySize) {
array = new byte[arraySize];
super.count = arraySize;
allocateMemory();
}
}
Помимо добавления конструкторов, если вы хотите инициализировать с помощью указателя, этого должно быть достаточно.
Однако вы можете сделать версию с одной структурой, сделав то же изменение FieldOrder
. Выделение памяти JNA зависит от порядка полей, определенного с помощью методов getFieldList()
и getFieldOrder()
(для которых новая аннотация удаляет стандартные требования).
Вы можете сохранить выделение массива в структуре, но изменить переопределение getFieldOrder()
, чтобы пропустить поле, определяющее массив в случае, если он имеет нулевой размер, сделав то же самое для getFieldList()
. Мне пришлось столкнуться именно с такой ситуацией, когда я писал структуру Sysinfo в сопоставлении JNA Linux LibC. Основная структура включает в себя это поле:
public byte[] _f = new byte[PADDING_SIZE];
Но переопределение getFieldList()
включает в себя:
if (PADDING_SIZE == 0) {
Iterator<Field> fieldIterator = fields.iterator();
while (fieldIterator.hasNext()) {
Field field = fieldIterator.next();
if ("_f".equals(field.getName())) {
fieldIterator.remove();
}
}
}
и getFieldOrder()
включает:
if (PADDING_SIZE == 0) {
fieldOrder.remove("_f");
}
Аналогичные условия удаляют поле _f_unused
в структуре Statvfs
в том же файле.
В случае вашей структуры, если вы знаете count
перед созданием экземпляра структуры, что-то вроде этого должно работать (непроверено):
public static class VarArray extends Structure {
public byte dummy0;
public short dummy1;
public int count;
public byte[] array = new byte[1];
public VarArray(int arraySize) {
array = new byte[arraySize];
count = arraySize;
allocateMemory();
}
@Override
protected List<String> getFieldOrder() {
if (count == 0) {
return List.of("dummy0", "dummy1", "count");
} else {
return List.of("dummy0", "dummy1", "count", "array");
}
}
// do the same for getFieldList()
}
Хорошее решение! Я должен добавить это в закладки, когда это снова появится.
Оказывается, я не могу динамически изменять список полей, так как он статически кэшируется. Я обновил решение с переопределениями для ensureAllocated
/readField
/writeField
.
Спасибо подробный ответ. Я использовал ваши предложения для хорошего решения, которое работает для моего случая - обновлено в вопросе.