c - haskell FFI : Wrapping a C struct containing a separately allocated string (char*)

标签 c haskell ffi storable haskell-ffi

假设你有一个 C 结构体

typedef struct {
  uint32_t num;
  char*    str;
} MyStruct;

和一个对其进行一些操作的函数f

void f(MyStruct* p);

C API 要求在调用 f 之前为 char* 分配足够的缓冲区:

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

并编写一个可存储实例。我的想法是在末尾分配 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 这样的工具, c2hsbindings-dsl , 或 greencard等,应该使用。

最佳答案

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

关于c - haskell FFI : Wrapping a C struct containing a separately allocated string (char*),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53534340/

相关文章:

haskell - 从 Haskell 中的 do block 返回之前是否可以检查某些条件?

rust - 在 Rust FFI 中解析联合结构

haskell - Haskell 与 c 的接口(interface)

c - tar.gz 文件出现意外的文件结尾

c - 如何使用 TOS 设置消息优先级?

c - Matlab 在 C 中生成的 Dll

haskell - Haskell 中的非详尽模式,创建的数据类型

haskell - 当单个 MVar 存在大量争用时,forkIO 线程模式下的内存占用

rust - 将包含字符串数组的 C 符号从 Rust 公开到 C

c - 在 C 中动态分配二维数组