gpt4 book ai didi

java - JNA如何在要传递给 native 库的结构中填充指向结构的指针字段?

转载 作者:行者123 更新时间:2023-11-30 01:53:42 25 4
gpt4 key购买 nike

我需要将JNA结构传递到本机层,该本机层包含一个指向结构的指针字段(可能包含零个或多个结构)。

这是“父”结构:

public class VkRenderPassCreateInfo extends Structure {
public int attachmentCount;
public VkAttachmentDescription.ByReference pAttachments;
}


(为简洁起见,省略了其他字段@FieldOrder和ByReference / Value类)

这是“孩子”结构:

public class VkAttachmentDescription extends Structure {
public int flags;
// ... lots and lots of other simple fields
}


根据JNA文档( here),指向数组的指针字段应为 Structure.ByReference字段。

在其他帖子中,填充此字段的标准方法是:


通过引用将字段初始化为结构
使用 Structure::toArray从字段分配结构数组
填充数组


所以:

// Init structure fields
renderPass.pAttachments = new VkRenderPassCreateInfo.ByReference();
renderPass.attachmentCount = size;

// Allocate memory
VkAttachmentDescription[] attachments = (VkAttachmentDescription[]) renderPass.pAttachments.toArray(size);

// Populate array
for(int n = 0; n < size; ++n) {
attachments[n].flags = ...
// and so on for other fields
}


1-这是在结构内初始化和分配指向结构的指针字段的正确方法吗?似乎有很多混帐?

2-上面的方法对于摆弄大小的结构很好用,但是我要处理的一些结构具有大量的字段,子结构等。我假设我可以在Java端构建一系列JNA结构,并且将它们直接设置到父结构中,但是 toArray方法意味着我必须将所有内容复制到生成的数组中?是否有更好/更简单的方法,这意味着我不必创建和复制本来就已经在Java端已经拥有的数据?

3-JNA提供了一个 StringArray帮助器类,该类处理结构中字符串数组的类似情况:

// Array of strings maintained on the Java side
List<String> strings = ...

// Nice and easy means of populating the JNA structure
structure.pStrings = new StringArray(strings.toArray(String[]::new));
...

// Send the data
library.fireandForget(structure);


这是我试图通过上面的结构代码实现的某种排序,但显然仅是针对字符串的情况-我是否错过了其他类似的帮助程序?

请注意,以上内容将结构传递给本机层,我没有尝试检索任何内容。

编辑1:只是为了弄清这个问题的要点-尽管上述工作有效,但除最琐碎的情况外,其他所有代码都会使样板代码产生问题。我正在努力找出最简单/最佳的方法来构建要传递给本机方法的复杂结构图。似乎缺少示例或教程,或者也许我不是在问正确的问题(?)任何指向示例,教程或传递包含其他结构的指针的结构的示例代码的指针。

编辑2:因此,当我调用本机库时,我尝试了多种方法,所有这些方法均导致 Illegal memory access错误。

我要发送的数据是由应用程序构造的-它可以是构建器模式,用户的选择等等。无论如何,结果是 VkAttachmentDescription的列表,然后我需要将其作为指针发送到-parent“ VkRenderPassCreateInfo”中的-structure字段。

在Java端使用JNA VkAttachmentStructure的原因是,其中一些结构包含大量字段。即调用 Structure::toArray然后逐字段填充结果数组是站不住脚的:代码量巨大,容易出错且更改起来很脆弱(例如忘记复制新字段)。我可以创建另一个类来抽象JNA类,但这只会解决问题。

这是代码的作用:

// Application builds the attachments
final List<VkAttachmentDescription> attachments = ...

...

// At some point we then send the render pass including the attachments

// Populate the render pass descriptor
final VkRenderPassCreateInfo info = new VkRenderPassCreateInfo();
info.pAttachments = ??? <--- what?
// ... other fields

// Send the descriptor
library.sendRenderPass(info);


尝试1:天真地将指向结构的指针设置为数组:

final VkRenderPassCreateInfo info = new VkRenderPassCreateInfo();
final var array = attachments.toArray(VkAttachmentDescription.ByReference[]::new);
info.pAttachments = array[0];
library.sendRenderPass(info);


结果是内存访问错误,我没想到这会起作用!

尝试2:使用Structure :: toArray(int)并将字段设置为第一个元素

final VkAttachmentDescription.ByReference[] array = (VkAttachmentDescription.ByReference[]) new VkAttachmentDescription.ByReference().toArray(attachments.size());

for(int n = 0; n < size; ++n) {
array[n] = attachments.get(n);
}

info.pAttachments = array[0];

library.sendRenderPass(info);


结果相同。

尝试3:使用Structure :: toArray(array)

toArray中有一个替代的 Structure方法,该方法采用数组,但是它似乎与调用整数版本没有什么不同?

尝试4:逐字段复制

final VkAttachmentDescription.ByReference[] array = (VkAttachmentDescription.ByReference[]) new VkAttachmentDescription.ByReference().toArray(attachments.size());

for(int n = 0; n < size; ++n) {
array[n].field = attachments.get(n).field;
// ...lots of other fields
}

info.pAttachments = array[0];

library.sendRenderPass(info);


这有效,但令人讨厌。

我显然完全不了解JNA。我的主要要点是 Structure::toArray创建一个必须逐字段填充的空结构数组,但是我已经拥有一个填充了所有内容的结构数组-如何将指针指向结构字段设置为该数组(即等效于 StringArray助手)?在我的脑海中似乎做一件如此简单的事情,但我根本找不到任何如何做自己想做的事的例子(除了琐碎的逐场复制的例子)。

令我困扰的另一件事是,父结构字段必须为 ByReference,这意味着代码中的所有其他结构都必须被引用?再次感觉就像我做错了所有事情。

最佳答案

您需要解决的问题(以及Illegal memory access错误的来源)是,接受您的数组的C端代码期望连续内存块中的Pointer。在C语言中,只需要第一个元素的内存地址,再加上大小偏移量即可;要访问array [1],您会找到array [0]的内存,并偏移结构的大小。

在您的情况下,您已为该块中的每个结构分配了非连续内存:

// Application builds the attachments
final List<VkAttachmentDescription> attachments = ...


每个 VkAttachmentDescription都映射到其自己的内存,并且在第一个结构的末尾尝试读取内存会导致错误。如果在实例化这些 VkAttachmentDescription对象时无法控制使用哪个内存,最终将导致复制内存需求,并且必须将本机内存从非连续块复制到连续块。

编辑添加:正如您在其他答案中指出的那样,如果您仅在Java端使用 VkAttachmentDescription结构并且未将其传递给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类声明,并使用 toArray()创建Structure数组。
 您已在代码中列出了使用ByReference类型创建数组的一种方法。可行,或者您也可以使用(默认ByValue)类型创建它,然后将Pointer提取到第一个元素,以在将其分配给结构字段时创建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结构,所以我认为它可以照常工作,但是如果有内置Java,则可能需要在上述循环的结尾处调用 array[n].read()从C读取到Java。到我不知道的C复制。

响应您的“父结构字段必须为ByReference”:如上所示, Pointer映射起作用,并以“类型安全”和(或可能不是)“可读性”为代价提供了更多的灵活性。您不需要在其他地方使用 ByReference,正如我在 toArray()所示的那样,您只需要在“结构”字段中使用它即可(您可以将其定义为 Pointer并完全消除对 ByReference的需要...但是如果您要这样做,为什么不只复制到 Memory缓冲区呢?我在这里打败一匹死马!)。

最后,如果您知道最终将拥有多少个元素(或该数字的上限),则理想的解决方案是从一开始就使用连续内存实例化数组。然后,您无需创建新的 VkAttachmentDescription实例,而只需从数组中获取一个预先存在的实例。只要您从头开始连续使用它们,就可以过度分配并且不使用它们,这没关系。您要传递给C的全部是结构数和第一个结构的地址,它并不关心您是否有多余的字节。

关于java - JNA如何在要传递给 native 库的结构中填充指向结构的指针字段?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55184654/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com