假设你有一个 C-struct
typedef struct {
uint32_t num;
char* str;
} MyStruct;
以及对其进行一些操作的函数f
,
void f(MyStruct* p);
C API 要求char*
在调用之前分配足够的缓冲区f
:
char buf[64]; //the C API docs say 64
MyStruct s = {1, buf};
f(s); // would go badly if MyStruct.str isn't alloc'ed
(请注意,该num
字段在此构造示例中没有任何用途。它只是防止使用CString
and的简单解决方案CStringLen
。)
问题是如何为这种 C API 编写 Haskell FFI。
我想出的是:从
data MyStruct = MyStruct {
num :: Word32,
str :: String
} deriving Show
并编写一个可存储的实例。我的想法是在最后分配 64 个字节作为字符串的缓冲区:
instance Storable MyStruct where
sizeOf _ = 8{-alignment!-} + sizeOf (nullPtr :: CString) + 64{-buffer-}
alignment _ = 8
poke
必须更改 str 中的指针以指向分配的缓冲区,然后必须将 Haskell 字符串复制到其中。我这样做withCStringLen
:
poke p x = do
pokeByteOff p 0 (num x)
poke strPtr bufPtr
withCStringLen (str x) $ \(p',l) -> copyBytes bufPtr p' (l+1) -- not sure about the +1
where strPtr = castPtr $ plusPtr p 8 :: Ptr CString
bufPtr = castPtr $ plusPtr p 16 :: CString
最后是peek
,这很简单:
peek p = MyStruct
<$> peek (castPtr p)
<*> peekCAString (castPtr $ plusPtr p 16)
所有这些都有效,但我觉得它相当难看。这是这样做的方法,还是有更好的方法?
如果有人想玩它,小玩具问题在github 上。
更新
chi
正如以下警告所指出的那样:使用硬编码的对齐和偏移是不好的做法。它们很脆弱并且依赖于平台/编译器。相反,应该使用c2hsc、c2hs 或bindings-dsl或greencard等工具。