具有c2hs的Haskell FFi:更好地对结构进行编组

时间:2018-12-14 10:21:29

标签: c haskell struct ffi c2hs

假设您具有提供C结构的C API

typedef struct A {
    int i;
    float f;
} A;

和填充它的函数:

void getA(A* a);

例如,这可能是从C API的内部获取某些信息的吸气剂。

在Haskell中,C结构将被镜像

data A = A {
    i :: Int,
    f :: Float
}

Storable实例是

instance Storable A where
    sizeOf    _ = {#sizeof  A #}
    alignment _ = {#alignof A #}
    peek p = ...
    poke p x = ...

窥视和戳戳与往常一样,由c2hs处理{#get...#}{#set #}编译指示。

Haskell函数getA :: IO A应该类似于

{#fun unsafe getA as getA {alloca- `A' peek*} -> `()'#}

除了这不起作用,因为c2hs创建了此绑定:

foreign import ccall unsafe "include/A.h getA"
    _getA'_ :: Ptr () -> IO ()

它的第一个参数为Ptr ()。可以通过以下方式来解决

{#fun unsafe getA as getA {allocaA- `A' peekA*} -> `()'#}

peekA :: Ptr () -> IO A
peekA = peek . castPtr

allocaA :: (Ptr () -> IO a) -> IO a
allocaA f = alloca $ \(p :: Ptr A) -> f (castPtr p)

allocaA很重要,因为如果使用了A,它可以确保分配()的内存而不是仅分配alloca的内存。

尽管这有效,但它有些乏味,并且如果您忘记写allocaXYZ而不是仅仅写alloca,则可以保证存在段错误。 (我刚刚发现花了很多时间来追踪这样的错误。)

我希望找到会产生

{#fun...#}咒语
foreign import ccall unsafe "include/A.h getA"
    _getA'_ :: Ptr A -> IO ()

,其他所有内容都会自然地跟随(请注意,Ptr A而不是Ptr ())。但据我所知,只有{allocXYZ- 'XYZ' peekXYZ*}条路线。

所以问题是:可以使用c2hs更好地做到这一点吗?

1 个答案:

答案 0 :(得分:0)

您使用{#pointer ...#}挂钩吗?由于您不会像对待不透明指针那样对待A(即if可以正常访问,并且Storable实例是明确编写的),因此,您需要使用箭头形式:

{#pointer *A as APtr -> A#}

到那时,您仍然必须使用alloca / peek函数来编组A,但是您的第一个理想的{#fun ...#}钩应该按编写的方式工作。 (您最终会忽略APtr类型;它会在生成的代码中显示,但在*.chs文件中不是必需的。)

还要注意,您需要将该指针定义添加到每个使用的文件A中;即使您从一个主文件中导出APtr,您仍然需要在使用它的所有位置添加{#pointer *A as APtr -> A nocode#}