假设你有一个 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
字段在此构造的示例中没有任何用途。它只是防止使用 CString
和 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等,应该使用。
最佳答案
虽然您的解决方案对我来说似乎相当不错(您将内存摆弄隐藏在 Storable
实例中,因此用户不应该费心自己寻找内存缓冲区),您也可以模仿 C 解决方案allocaBytes
.
关于c - haskell FFI : Wrapping a C struct containing a separately allocated string (char*),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53534340/