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

mcm*_*yer 5 c haskell ffi storable haskell-ffi

假设你有一个C结构

typedef struct {
  uint32_t num;
  char*    str;
} MyStruct;
Run Code Online (Sandbox Code Playgroud)

以及对其f执行某些操作的函数,

void f(MyStruct* p);
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

(请注意,该num字段在此构造示例中没有任何用途.它只是防止使用CString和的简单解决方案CStringLen.)

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

我想出的是:开始

data MyStruct = MyStruct {
    num :: Word32,
    str :: String
} deriving Show
Run Code Online (Sandbox Code Playgroud)

并写一个可存储的实例.我的想法是在末尾分配64个字节,它将作为字符串的缓冲区:

instance Storable MyStruct where
    sizeOf _ = 8{-alignment!-} + sizeOf (nullPtr :: CString) + 64{-buffer-}
    alignment _ = 8
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

最后这里peek是直截了当的:

peek p = MyStruct 
     <$> peek (castPtr p)
     <*> peekCAString (castPtr $ plusPtr p 16)
Run Code Online (Sandbox Code Playgroud)

这一切都有效,但我觉得它很难看.这是做到这一点的方式,还是有更好的方法?

如果有人想玩它,小玩具问题就在github上.

更新

正如chi以下警告所指出的那样:使用硬编码对齐和偏移是不好的做法.它们是脆弱的并且依赖于平台/编译器.相反,像工具c2hsc,c2hs绑定DSL的,或绿卡等,应使用.

arr*_*owd 0

虽然你的解决方案对我来说似乎相当不错(你将内存摆弄隐藏在Storable实例中,这样用户就不必费心自己寻找内存缓冲区),但你也可以使用allocaBytes.