Haskell - FFI和指针

5
我使用FFI来调用一个C函数,它接受一个结构体并返回同样的结构体。我看到的参考资料都说我必须使用这些结构体的指针才能将其导入Haskell。例如:
data Bar = Bar { a :: Int, b :: Int }
type BarPtr = Ptr (Bar)

foreign import ccall "static foo.h foo"
    f_foo :: BarPtr -> BarPtr

现在我有一个问题,需要使用该函数。我看到的参考链接中,这些函数类型为 BarPtr -> IO ()并使用了 with ,其签名为 Storable a => a -> (Ptr a -> IO b) -> IO b,这是可以的,因为它们在main函数内部调用该函数。
然而,我想将这个函数包装成一个库,获得一个没有IO的 Bar -> Bar 类型的函数,是否可以在不使用 unsafePerformIO 的情况下实现呢?程序流程是什么?

如果你想从类似于 Ptr A -> IO () 的东西得到一个纯函数,那么相应的 C 函数必须是“几乎纯”的,也就是说它唯一的效果就是修改其参数指向的内存。如果满足这个条件,你可以使用 Storable 和 alloca 来创建一个指向 C 的指针,然后从该指针读取并返回值来编写类型为 A -> A 的函数。这个函数在道德上是纯洁的,因为它没有可观察的影响,所以在它上面调用 unsafePerformIO 是完全可以的(实际上这就是 unsafePerformIO 的预期使用方式)。 - user2407038
1个回答

7

如果不使用 unsafePerformIO,则无法从类型中删除 IO。但是,在此情况下,可以获得所需类型的函数,但有一些注意事项。具体而言,C 函数“foo”不能依赖于任何全局变量、线程本地状态或除单个参数之外的任何内容。此外,当 bar 未更改时,调用 foo(bar) 应始终提供相同的结果。

我希望尝试导入 C 函数。

bar foo(bar input);

使用此调用

f_foo :: BarPtr -> BarPtr

由于结果类型的原因,这会导致编译错误。我认为你可能需要编写一个包装函数(用C语言):

void wrap_foo(bar *barPtr) {
    bar outp = foo(*barPtr);
    *barPtr = outp;
}

并将其导入为

f_wrap_foo :: BarPtr -> IO ()

最后,您需要使用以下方式调用此导入函数:
fooBar :: Bar -> Bar
fooBar bar = unsafePerformIO $ with bar $ \barPtr -> do
    f_wrap_foo barPtr
    peek barPtr

1
测试并获得批准!谢谢! - guaraqe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接