Haskell FFI: Wrapping a C struct containing a separately allocated string (char*)

249 Views Asked by At

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.

1

There are 1 best solutions below

3
On

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 with allocaBytes.