首页 > 解决方案 > 带有 c2hs 的 Haskell FFi:更好的结构外编组

问题描述

假设您有一个提供 C 结构的 C API

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

以及填充它的函数:

void getA(A* a);

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

在 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 = ...

peek 和 poke 与c2hs 处理的{#get...#}和pragma 一样正常。{#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分配内存而不是仅使用()if的内存alloca

虽然这可行,但它有点乏味,而且如果你忘记写allocaXYZ而不是只写alloca. (我刚刚看到花了很多时间来追踪一个这样的错误。)

我希望找到一个{#fun...#}产生的咒语

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

其他一切都会自然而然地遵循(注意Ptr A代替Ptr ())。但据我所知,只有{allocXYZ- 'XYZ' peekXYZ*}路线。

所以问题是:这可以用更好的方式完成c2hs吗?

标签: chaskellstructffic2hs

解决方案


你在用{#pointer ...#}钩子吗?由于您没有将 A 视为不透明指针(即可以正常访问并且i实例是显式编写的),因此您需要使用箭头形式:fStorable

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

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

另请注意,您需要将该指针定义添加到A使用的每个文件中;即使您APtr从一个主文件导出,您仍然需要{#pointer *A as APtr -> A nocode#}在使用它的任何地方添加。


推荐阅读