Suppose you have a C-struct
typedef struct {
uint32_t num;
char* str;
} MyStruct;
and a function f
that does some operation on it,
void f(MyStruct* p);
The C API demands that the char*
be allocated a sufficient buffer before calling 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
(Note that the num
field has no purpose in this constructed example. It just prevents the trivial solution using CString
and CStringLen
.)
The question is how to write a Haskell FFI for this kind of C API.
What I've come up with is this: Start with
data MyStruct = MyStruct {
num :: Word32,
str :: String
} deriving Show
and write a Storable instance. My idea is to allocate 64 bytes at the end which will serve as buffer for the string:
instance Storable MyStruct where
sizeOf _ = 8{-alignment!-} + sizeOf (nullPtr :: CString) + 64{-buffer-}
alignment _ = 8
poke
has to change the pointer in str to point to the allocated buffer, and then the Haskell string has to be copied into it. I do this with 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
Finally here's peek
, which is straightforward:
peek p = MyStruct
<$> peek (castPtr p)
<*> peekCAString (castPtr $ plusPtr p 16)
All this works, but I find it rather ugly. Is this the way to do it, or is there a better way?
If anyone wants to play with it, the little toy problem is on github.
Update
As pointed out by chi
the following caveat is in order: Using hard-coded alignments and offsets is bad practice. They are fragile and platform/compiler dependent. Instead, tools like c2hsc, c2hs or bindings-dsl, or greencard, etc., should be used.
While your solution seems quite nice for me (you are hiding that memory fiddling inside
Storable
instance, so that user shouldn't bother finding a memory buffer himself), you can mimic C solution too withallocaBytes
.