使用 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 @、bar
和 baz
。
据我所知,没有一个标准的 Clojure Java 互操作工具可以创建上述类。
创建一个继承自 Structure
的类并覆盖结构字段 getter/setter 方法。由于gen-class
是(我相信)唯一允许直接继承的 Clojure 构造,并且它不支持多个公共非静态字段,因此下一个替代方案是根本不使用字段。 Looking at the Structure
abstract class documentation,似乎有可能使用“虚拟”结构字段的覆盖混合物,这样它就可以真正从不同的来源(such as the state
field from gen-class
)获取和设置数据。浏览文档,似乎覆盖了readField
、writeField
,以及其他一些方法可能有预期的效果,但我不清楚如何通过阅读文档来做到这一点,我网上找不到类似的例子。
使用不同的存储类。 JNA 有无数用于包装原生类型的类。我想知道,除了定义和使用Structure
类之外,我是否可以使用另一个可以采用任意位数的泛型类(例如Integer
如何容纳4 位宽的任何东西,而不管是什么类型源'实际上'是)。例如,是否可以说一个函数返回一个长度为 16 的字节数组(因为sizeof(MyStruct)
是 16)?在实现NativeMapped
的容器类中包装一个固定大小的数组怎么样?我也找不到如何做的例子。
【问题讨论】:
JNA 以com.sun.jna.Memory
的形式提供块内存分配。从那里,您可以使用Pointer.getXXX()
方法从内存中的任意偏移中提取任意类型。
@technomage 使用Memory
作为createStruct
的返回类型返回一个Pointer
,其地址为0x0000000400000003
。换句话说,它将结构的原始内存值视为指针。取消引用时(通过任何 read
或 getXXX
方法),它会使 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 中按值获取和传递结构的主要内容,如果未能解决你的问题,请参考以下文章