您需要解决的问题(以及问题的来源)Illegal memory access
错误)是接受数组的 C 端代码期望Pointer
to a 连续块的记忆。在C中,只需要第一个元素的内存地址,加上大小偏移量;要访问 array[1],您可以找到 array[0] 的内存以及结构体大小的偏移量。
在您的情况下,您已经为此块中的每个结构分配了非连续内存:
// Application builds the attachments
final List<VkAttachmentDescription> attachments = ...
Each VkAttachmentDescription
映射到它自己的内存,并且尝试读取第一个结构末尾的内存会导致错误。如果您无法控制在这些情况下使用哪些内存VkAttachmentDescription
实例化对象后,您最终将重复内存需求,并且必须将本机内存从非连续块复制到连续块。
编辑添加:正如您在其他答案中所指出的,如果您只使用过VkAttachmentDescription
Java端的结构并没有将其传递给C函数,本机内存可能还没有被写入。以下解决方案基于Pointer.get*()
方法直接从 C 内存读取,因此它们需要write()
在某个时刻进行调用。
假设你别无选择,只能从List<VkAttachmentDescription>
,你需要做的第一件事是分配C需要的连续内存。让我们得到我们需要的字节大小:
int size = attachments.size();
int bytes = attachments.get(0).size();
我们需要分配size * bytes
的记忆。
这里有两个选择:使用 a 直接分配内存Memory
对象(的子类Pointer
)或使用Structure.toArray
。对于直接分配:
Memory mem = new Memory(size * bytes);
我们可以直接使用mem
as a 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);
}
(性能权衡:数组实例化开销与 JavaC 调用。)
现在缓冲区已写入,您可以将其发送到 C,它需要结构数组,但它不会知道其中的区别...字节就是字节!
编辑添加:我认为可以使用更改本机内存支持useMemory()
然后直接写入新的(连续的)位置。这段代码未经测试,但我怀疑可能实际上有效:
for (int n = 0; n < size; ++n) {
attachments.get(n).useMemory(mem, n * bytes);
attachments.get(n).write();
}
就我个人而言,由于我们只是复制已经存在的东西,所以我更喜欢这个Memory
基于映射。然而......有些程序员是受虐狂。
如果你想更加“类型安全”,你可以使用ByReference
结构内部的类声明并使用创建结构数组toArray()
。
您已在代码中列出了使用 ByReference 类型创建数组的一种方法。这可行,或者您也可以使用(默认 ByValue)类型创建它,然后在将其分配给结构字段时提取指向第一个元素的指针以创建 ByReference 类型:
VkAttachmentDescription[] array =
(VkAttachmentDescription[]) new VkAttachmentDescription().toArray(attachments.size());
然后你可以这样设置:
info.pAttachments = new VkAttachmentDescription.ByReference(array[0].getPointer());
在这种情况下,将值从列表(由单独分配的内存块支持的结构)复制到数组(连续内存)会更复杂一些,因为内存映射的类型更窄,但它遵循与为了Memory
映射。您发现的一种方法是手动复制结构的每个元素! (呃。)另一种可以避免复制/粘贴错误的方法是使用反射(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 的只是结构的# 和第一个结构的地址,它并不关心您是否有额外的字节。