简体   繁体   English

Haskell FFI:包装一个包含单独分配字符串的C结构(char *)

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

Suppose you have a C-struct 假设你有一个C结构

typedef struct {
  uint32_t num;
  char*    str;
} MyStruct;

and a function f that does some operation on it, 以及对其执行某些操作的函数f

void f(MyStruct* p);

The C API demands that the char* be allocated a sufficient buffer before calling f : 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

(Note that the num field has no purpose in this constructed example. It just prevents the trivial solution using CString and CStringLen .) (注意, num字段在这个构造的例子中没有用处。它只是阻止了使用CStringCStringLen的简单解决方案。)

The question is how to write a Haskell FFI for this kind of C API. 问题是如何为这种C API编写Haskell FFI。

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: 我的想法是在末尾分配64个字节,它将作为字符串的缓冲区:

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. poke必须将str中的指针更改为指向已分配的缓冲区,然后必须将Haskell字符串复制到其中。 I do this with withCStringLen : 我用withCStringLen执行此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 ,这是直截了当的:

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 . 如果有人想玩它,小玩具问题就在github上

Update 更新

As pointed out by chi the following caveat is in order: Using hard-coded alignments and offsets is bad practice. 正如chi所指出的,下面的警告是有道理的:使用硬编码对齐和偏移是不好的做法。 They are fragile and platform/compiler dependent. 它们是脆弱的并且依赖于平台/编译器。 Instead, tools like c2hsc , c2hs or bindings-dsl , or greencard , etc., should be used. 相反,像工具c2hscc2hs绑定DSL的 ,或绿卡等,应使用。

虽然你的解决方案对我来说似乎相当不错(你隐藏在Storable实例中的内存,因此用户不应该费心去寻找内存缓冲区),你也可以使用allocaBytes模仿C解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM