Проблема, которую нужно решить (и источник ошибок Illegal memory access
), заключается в том, что код на стороне C, принимающий ваш массив, ожидает от Pointer
до непрерывного блока памяти. В C вам нужен только адрес памяти первого элемента плюс смещение размера; для доступа к массиву [1] вы найдете память массива [0] и смещены на размер структуры.
В вашем случае вы выделили несмежную память для каждой структуры в этом блоке:
// Application builds the attachments
final List<VkAttachmentDescription> attachments = ...
Каждый VkAttachmentDescription
сопоставлен с собственной памятью, и попытка чтения памяти в конце первой структуры вызывает ошибку. Если у вас нет контроля над тем, какая память используется при создании экземпляров этих объектов VkAttachmentDescription
, вы в конечном итоге продублируете свои требования к памяти и будете вынуждены копировать собственную память из несмежного блока в непрерывный блок.
Отредактировано, чтобы добавить: Как указано в вашем другом ответе, если вы работали только со структурой VkAttachmentDescription
на стороне Java и не передавали ее в функцию C, собственная память может не было написано. Приведенные ниже решения, основанные на методах Pointer.get*()
, считываются непосредственно из памяти C, поэтому в какой-то момент им потребуется вызов write()
.
Если у вас нет другого выбора, кроме как начать с List<VkAttachmentDescription>
, первое, что вам нужно сделать, - это выделить непрерывную память, в которой нуждается C. Получим нужные нам байтовые размеры:
int size = attachments.size();
int bytes = attachments.get(0).size();
Нам нужно выделить size * bytes
памяти.
У вас есть два варианта: непосредственно выделить память, используя объект Memory
(подкласс Pointer
) или использовать Structure.toArray
. Для прямого распределения:
Memory mem = new Memory(size * bytes);
Мы можем напрямую использовать mem
как Pointer
, если мы определим ссылку следующим образом:
public class VkRenderPassCreateInfo extends Structure {
public int attachmentCount;
public Pointer pAttachments;
}
Тогда все просто:
info.pAttachments = mem;
Теперь осталось только скопировать байты из несмежной памяти в выделенную память. Мы можем сделать это побайтово (проще видеть, что происходит на уровне байтов на стороне C):
for (int n = 0; n < size; ++n) {
Pointer p = attachments.get(n).getPointer();
for (int b = 0; b < bytes; ++b) {
mem.setByte(n * bytes + b, p.getByte(b));
}
}
Или мы можем сделать это по структуре:
for (int n = 0; n < size; ++n) {
byte[] attachment = attachments.get(n).getPointer().getByteArray(0, bytes);
mem.write(n * bytes, attachment, 0, bytes);
}
(Компромисс производительности: затраты на создание экземпляра массива по сравнению с вызовами Java <-> C.)
Теперь, когда буфер записан, вы можете отправить его в C, где он ожидает массив структур, и он не будет знать разницу ... байты - это байты!
Отредактировано, чтобы добавить: Я думаю, что можно изменить исходную память, используя useMemory()
, а затем напрямую записать в новое (непрерывное) местоположение. Этот код не проверен, но я подозреваю, что он может действительно работать:
for (int n = 0; n < size; ++n) {
attachments.get(n).useMemory(mem, n * bytes);
attachments.get(n).write();
}
Лично, поскольку мы просто делаем копию того, что уже существует, я бы предпочел это отображение на основе Memory
. Однако ... некоторые кодеры являются мазохистами.
Если вы хотите быть немного более «безопасным по типу», вы можете использовать объявление класса ByReference
внутри структуры и создать массив Structure, используя toArray()
.
Вы перечислили в своем коде один из способов создания массива с использованием типа ByReference. Это работает, или вы также можете создать его с типом (по умолчанию ByValue), а затем извлечь указатель на первый элемент позже, чтобы создать тип ByReference при назначении его полю структуры:
VkAttachmentDescription[] array =
(VkAttachmentDescription[]) new VkAttachmentDescription().toArray(attachments.size());
Тогда вы можете установить его так:
info.pAttachments = new VkAttachmentDescription.ByReference(array[0].getPointer());
В этом случае немного сложнее копировать значения из списка (структур, поддерживаемых индивидуально выделенными блоками памяти) в массив (из смежной памяти), поскольку отображение памяти более узко типизировано, но оно следует затот же шаблон, что и для отображения Memory
.Один из способов, который вы обнаружили, - это вручную скопировать каждый элемент структуры!(Тьфу.) Другой способ, который может уберечь вас от некоторых ошибок копирования / вставки, - это использовать Reflection (что JNA делает под капотом).Это тоже большая работа, которая дублирует то, что делает JNA, так что это уродливо и подвержено ошибкам.Тем не менее, все еще возможно копировать необработанные собственные байты из несмежного блока памяти в смежный.(В этом случае ... почему бы просто не перейти прямо к Memory
, но мое смещение показывает.) Вы можете перебирать байты, как в примере Memory
, например:
for (int n = 0; n < size; ++n) {
Pointer p = attachments.get(n).getPointer();
Pointer q = array[n].getPointer();
for (int b = 0; b < bytes; ++b) {
q.setByte(b, p.getByte(b));
}
}
Или выможет читать байты в виде фрагментов следующим образом:
for (int n = 0; n < size; ++n) {
byte[] attachment = attachments.get(n).getPointer().getByteArray(0, bytes);
array[n].getPointer().write(0, attachment, 0, bytes);
}
Обратите внимание, что я не тестировал этот код;он пишет на нативную сторону, но не в структуру Java, поэтому я думаю, что он будет работать как есть, но вам может понадобиться вызов array[n].read()
в конце цикла выше для чтения из C в Java в случае, если есть встроенная JavaСкопировать в C, о котором я не знаю.
В ответ на ваше «поле родительской структуры должно быть ByReference»: как показано выше, отображение Pointer
работает и обеспечивает немного большую гибкость за счет затрат.«безопасность типов» и, возможно, (или нет) «читабельность».Вам не нужно использовать ByReference
где-то еще, как я показал с toArray()
, где он нужен только для поля структуры (которое можно просто определить как Pointer
и полностью исключить необходимость в ByReference
... но если вы делаете это, почему бы просто не скопировать в буфер Memory
? Я бью мертвую лошадь здесь!).
Наконец, идеальным решением, если вы знаете, сколько элементов у вас будет в итоге (или верхняя граница этого числа), было бы создание экземпляра массива с использованием непрерывной памяти в самом начале.Тогда вместо создания нового экземпляра VkAttachmentDescription
вы можете просто извлечь уже существующий экземпляр из массива.Это нормально, если вы перераспределяете и не используете их все, если вы используете их непрерывно с самого начала.Все, что вы передаёте C, это # структур и адрес первой, вам все равно, если у вас есть дополнительные байты.