使用 JNA 在 Clojure 中按值获取和传递结构

Posted

技术标签:

【中文标题】使用 JNA 在 Clojure 中按值获取和传递结构【英文标题】:Getting and passing structs by value in Clojure with JNA 【发布时间】:2014-09-18 13:07:14 【问题描述】:

我有一个 C API,我试图通过 JNA API 在 Clojure 中使用。我的问题可以通过以下示例得到最好的证明。假设我在库中有这个 C 代码:

typedef struct 
    int foo;
    int bar;
    double baz;
 MyStruct;

MyStruct createStruct() 
    MyStruct myStruct;
    myStruct.foo = 3;
    myStruct.bar = 4;
    myStruct.baz = 3.14;

    return myStruct;


double addStruct(MyStruct myStruct) 
    return myStruct.foo + myStruct.bar + myStruct.baz;

在本例中,我想调用createStruct,然后将结果传递给addStruct。这里的重点是MyStruct按值传递 作为返回类型和参数。在任何时候我都不需要实际读取MyStruct 中字段的值。

另外,在我的系统中,原生函数是这样包装的:

; `quux` is defined in `some-lib` and returns an `int`
(let [fn- (com.sun.jna.Function/getFunction "some-lib" "quux")]
  (fn [& args]
    (.invoke fn- Integer (to-array args))))

目标是获得一个类型来替代上面的Integer,它将MyStruct 包装为一个值。

我发现涵盖此主题的唯一资源是 this article,但它只讨论了如何通过引用传递结构。

鉴于此,以下是我尝试采取的不同方法来解决此问题

    创建一个继承自Structure的类,即JNA的built-in mechanism for creating and using structs。鉴于该页面上的信息,我尝试仅使用 Clojure 创建以下类:

    class MyStruct extends Structure implements Structure.ByValue 
        int foo;
        int bar;
        double baz;
    
    

    deftype 不适用于这种情况,因为该类需要从抽象类Structure 继承,而gen-class 不起作用,因为该类需要具有公共非静态字段@987654339 @、barbaz

    据我所知,没有一个标准的 Clojure Java 互操作工具可以创建上述类。

    创建一个继承自 Structure 的类并覆盖结构字段 getter/setter 方法。由于gen-class 是(我相信)唯一允许直接继承的 Clojure 构造,并且它不支持多个公共非静态字段,因此下一个替代方案是根本不使用字段。 Looking at the Structure abstract class documentation,似乎有可能使用“虚拟”结构字段的覆盖混合物,这样它就可以真正从不同的来源(such as the state field from gen-class)获取和设置数据。浏览文档,似乎覆盖了readFieldwriteField,以及其他一些方法可能有预期的效果,但我不清楚如何通过阅读文档来做到这一点,我网上找不到类似的例子。

    使用不同的存储类。 JNA 有无数用于包装原生类型的类。我想知道,除了定义和使用Structure 类之外,我是否可以使用另一个可以采用任意位数的泛型类(例如Integer 如何容纳4 位宽的任何东西,而不管是什么类型源'实际上'是)。例如,是否可以说一个函数返回一个长度为 16 的字节数组(因为sizeof(MyStruct) 是 16)?在实现NativeMapped 的容器类中包装一个固定大小的数组怎么样?我也找不到如何做的例子。

【问题讨论】:

JNA 以com.sun.jna.Memory 的形式提供块内存分配。从那里,您可以使用Pointer.getXXX() 方法从内存中的任意偏移中提取任意类型。 @technomage 使用Memory 作为createStruct 的返回类型返回一个Pointer,其地址为0x0000000400000003。换句话说,它将结构的原始内存值视为指针。取消引用时(通过任何 readgetXXX 方法),它会使 VM 崩溃。 问题在于struct 参数和返回值语义因编译器而异,并且本机代码实际上使用Structure 布局来确定如何正确设置堆栈。我建议您可以使用 Memory 将任意内存块分配给现有的 Structure.ByValue 实例。 你最好问问 clojure 的人。 JNA 需要在实际参数和库函数映射签名上都使用 Structure.ByValue 标记,以便将结构按值传递到本机代码。 如果您确实以某种方式绕过定义公共字段,您需要为您的结构覆盖(受包保护的)getTypeInfo(),它会返回 FFI 类型信息你的结构(它本身就是一个结构)。 【参考方案1】:

Clojure 实际上嵌入了 ASM 的分叉版本,用于生成和加载 JVM 字节码。

在这个库的基础上,我构建了小型 DSL(它不完整并且可能已损坏),它允许使用类似 Java 的语法创建任意 Java 类。目前只是一个GitHub gist,只支持扩展类、实现接口、添加调用超类构造函数的构造函数和字段。

这是上述问题的解决方案,使用此 DSL:

(def-class MyStruct :extends    com.sun.jna.Structure
                    :implements [com.sun.jna.Structure$ByValue]
  ; Declare all the constructors, which just forward their
  ; arguments to the constructors of com.sun.jna.Structure
  (com.sun.jna.Structure [])
  (com.sun.jna.Structure [com.sun.jna.TypeMapper])
  (com.sun.jna.Structure [Integer])
  (com.sun.jna.Structure [Integer com.sun.jna.TypeMapper])
  (com.sun.jna.Structure [com.sun.jna.Pointer])
  (com.sun.jna.Structure [com.sun.jna.Pointer Integer])
  (com.sun.jna.Structure [com.sun.jna.Pointer Integer com.sun.jna.TypeMapper])

  ; Declare the fields of the struct
  ^Integer foo
  ^Integer bar
  ^Double  baz)

现在,我可以执行以下操作:

(defn createStruct [& args]
  (let [fn- (com.sun.jna.Function/getFunction "some-lib" "createStruct")]
    (.invoke fn- MyStruct (to-array args))))

(defn addStruct [& args]
  (let [fn- (com.sun.jna.Function/getFunction "some-lib" "addStruct")]
    (.invoke fn- Double (to-array args))))

(addStruct (createStruct))
; => 10.14

【讨论】:

以上是关于使用 JNA 在 Clojure 中按值获取和传递结构的主要内容,如果未能解决你的问题,请参考以下文章

为啥要在 C++ 中按值传递对象 [重复]

是否传递指针参数,在 C++ 中按值传递?

在lisp中按值传递参数

为啥我要在 C 中按值传递函数参数?

在 Ruby 中按值传递是啥意思? [复制]

在C ++中的继承上下文中按值传递对象[重复]