Предположим, у вас есть C-struct
typedef struct {
uint32_t num;
char* str;
} MyStruct;
и функция f
, которая над ним работает,
void f(MyStruct* p);
API C требует, чтобы 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
.)
Вопрос в том, как написать FFI на Haskell для такого рода API C.
Я пришел к следующему: начните с
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 и т. Д.