5

假设你有一个 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字段在此构造示例中没有任何用途。它只是防止使用CStringand的简单解决方案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正如以下警告所指出的那样:使用硬编码的对齐和偏移是不好的做法。它们很脆弱并且依赖于平台/编译器。相反,应该使用c2hscc2hsbindings-dslgreencard等工具。

4

1 回答 1

0

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

于 2018-11-29T08:38:17.797 回答