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

Java 使用JNA在Clojure中按值获取和传递结构,java,struct,clojure,jna,pass-by-value,Java,Struct,Clojure,Jna,Pass By Value,我试图通过JNAAPI在Clojure中使用一个C API。下面的例子最能说明我的问题。假设我在库中有此C代码: typedef struct { int foo; int bar; double baz; } MyStruct; MyStruct createStruct() { MyStruct myStruct; myStruct.foo = 3; myStruct.bar = 4; myStruct.baz = 3.14;

我试图通过JNAAPI在Clojure中使用一个C API。下面的例子最能说明我的问题。假设我在库中有此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
包装为一个值。

我找到的关于这个主题的唯一资源是,但它只讨论了如何通过引用传递结构

考虑到这一点,以下是我试图采取的不同方法来解决这个问题

  • 创建一个继承自JNA结构的类。根据该页面上的信息,我尝试仅使用Clojure创建以下类:

    class MyStruct extends Structure implements Structure.ByValue {
        int foo;
        int bar;
        double baz;
    }
    
    deftype
    不适用于此场景,因为类需要从抽象类
    结构继承,而
    gen类
    不起作用,因为类需要具有公共非静态字段
    foo
    bar
    baz

    据我所知,任何标准Clojure Java互操作工具都无法创建上述类

  • 创建一个继承自
    结构的类,并重写结构字段getter/setter方法。由于
    gen class
    是(我相信)唯一允许直接继承的Clojure构造,并且它不支持多个公共非静态字段,因此下一个选择就是根本不使用字段,使用“virtual”结构字段似乎有多种覆盖,因此它可以从不同的源()获取和设置数据。翻阅文档,似乎覆盖了
    readField
    writeField
    ,以及其他一些方法可能会产生预期的效果,但我在阅读文档时不清楚如何做到这一点,我在网上找不到任何类似的例子

  • 使用不同的存储类别。JNA有大量用于包装本机类型的类。我想知道,与其定义和使用
    结构
    类,我是否可以使用另一个可以接受任意位数的泛型类(比如
    整数
    可以容纳任何4位宽的内容,而不管源“实际”是什么类型)。例如,是否可以假设函数返回长度为16的字节数组(因为
    sizeof(MyStruct)
    Is 16)?在实现的容器类中包装固定大小的数组怎么样?我也找不到这样做的例子


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

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

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