假设您有一个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
字段在此构造示例中没有用。它只是防止使用CString
和CStringLen
的琐碎解决方案。)
问题是如何为这种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
所指出的,需要进行以下警告:使用硬编码的对齐方式和偏移量是一种不好的做法。它们是脆弱的,并且依赖于平台/编译器。相反,应使用c2hsc,c2hs或bindings-dsl或greencard等工具。
答案 0 :(得分:0)
虽然您的解决方案对我来说似乎很好(您将内存摆在Storable
实例中,以使用户不必自己去寻找内存缓冲区),但是您也可以使用{{3 }}。