我对Haskell C FFI有疑问,特别是关于访问C库导出的静态数据结构。
我正在包装的C库具有静态数据结构,如下面的FOO_GEORGE
,以下列方式导出:
static struct foo_struct foo_table[] = { /* list of foo structs */ }
typedef struct foo_struct *foo_t;
foo_t FOO_GEORGE = &foo_table[0];
foo_t FOO_HARRY = &foo_table[1];
foo_t FOO_SUSAN = &foo_table[2];
/* ... */
我的Haskell库中需要的值是foo_struct
(&foo_table[n]
)的地址,即FOO_GEORGE
的内容,它放在一个不透明的newtype包装器中通常的方式(构造函数不是从库中导出的,只是类型):
newtype Foo = Foo { getFoo :: (Ptr Foo) }
这就是我现在正在做的事情:
foreign import ccall "&FOO_GEORGE" fooGeorgeHandle :: Ptr (Ptr Foo)
FooGeorge = Foo . unsafeDupablePerformIO . peek $ fooGeorgeHandle
我认为这是unsafePerformIO
的合理使用,因为C API和实现说明peek
的这种使用是纯粹的并且没有副作用。此外,我认为我不需要采取documentation(从{-# NOINLINE foo #-}
开始)的要点中列出的任何预防措施。
我的总体问题是:我做得对吗?上面的分析是否正确?有没有更好或更好的方法来做到这一点?如果foreign import
子句允许我执行指针引用,那将是很好的,但它似乎没有;我错过了什么吗?有人可能会说这会是一个糟糕的特性,因为如果指针很糟糕它可能会出现段错误 - 但是,我必须使用的peek
也是如此,所以它也是相同的。
谢谢!
答案 0 :(得分:2)
John L.建议使用CApiFFI
扩展,它完全符合我的要求:允许您导入值而不是位置。现在:
{-# LANGUAGE CApiFFI #-}
newtype {-# CTYPE "foo.h" "struct foo_struct" #-} Foo = Foo { getFoo :: (Ptr Foo) }
foreign import capi "foo.h value FOO_GEORGE" fooGeorgePtr :: Ptr a
fooGeorge = Foo fooGeorgePtr
另一个优点是无论FOO_GEORGE
是C变量还是预处理器宏,这都有效。我正在使用的C API使用两者,并且我链接的相同API的不同实现以不同方式执行,因此独立于此非常好。
但是,有一个绊脚石:CApiFFI
不适用于 ghci !问题是known,并且在GHC 8.0.1之前没有被修复(当我第一次写这个时,它应该是7.10,然后被推进)。我有一个非常hacky的解决方法。 CApiFFI
通过动态生成C库,编译和链接Haskell程序来工作。它完成后删除库; ghci 问题似乎是 .so 文件在 ghci 需要链接时消失了。我只是在编译之前抓取 .c 文件,然后才能删除它,然后自己编译并告诉 ghci 加载它。由于我不经常更改程序的这一部分,因此对我来说非常合适。
我捕获临时 .c 文件的方法是使用compilation-mode
在Emacs (setq compilation-auto-jump-to-first-error t)
中启动 ghci 。 Emacs看到错误并将 .c 文件加载到缓冲区中,然后GHC将其删除 - 当我看到文件消失时,我将内容放在缓冲区中。
更新: ghci -fobject-code Foo
有效,但只能看到从模块中导出的名称。