Haskell FFI:包装包含单独分配的字符串(char *)的C结构

时间:2018-11-29 08:04:21

标签: c haskell ffi storable haskell-ffi

假设您有一个C结构

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字段在此构造示例中没有用。它只是防止使用CStringCStringLen的琐碎解决方案。)

问题是如何为这种C API编写Haskell FFI。

我想出的是这个:从

开始
data MyStruct = MyStruct {
    num :: Word32,
    str :: String
} deriving Show

并编写一个Storable实例。我的想法是在末尾分配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所指出的,需要进行以下警告:使用硬编码的对齐方式和偏移量是一种不好的做法。它们是脆弱的,并且依赖于平台/编译器。相反,应使用c2hscc2hsbindings-dslgreencard等工具。

1 个答案:

答案 0 :(得分:0)

虽然您的解决方案对我来说似乎很好(您将内存摆在Storable实例中,以使用户不必自己去寻找内存缓冲区),但是您也可以使用{{3 }}。