Haskell C FFI:访问静态数据结构

时间:2014-08-19 19:52:53

标签: c haskell ffi

我对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也是如此,所以它也是相同的。

谢谢!

1 个答案:

答案 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有效,但只能看到从模块中导出的名称。