【问题标题】:Calling a C function that is referenced by a C pointer to that function with Haskell FFI使用 Haskell FFI 调用由指向该函数的 C 指针引用的 C 函数
【发布时间】:2013-03-04 14:28:59
【问题描述】:

'C'wiringPi 库中有一个函数类型,

extern void (*pinMode) (int pin, int mode) ;

我尝试使用带有 FunPtr 的 FFI 从 haskell 调用它。所以我做到了,

foreign import ccall unsafe "wiringPi.h &pinMode" c_pinMode 
        :: FunPtr (CInt -> CInt -> IO ())
foreign import ccall "dynamic" dc_pinMode 
        :: FunPtr (CInt -> CInt -> IO ()) -> (CInt -> CInt -> IO ())

但由于某种原因,即使它可以编译,它似乎也没有调用 'pinMode' 指向的函数。

所以我尝试使用普通的 Foreign.Ptr,我想我可以偷看 Ptr 以获取对“pinMode”指向的底层“C”函数的引用。所以我尝试了,

foreign import ccall "wiringPi.h &pinMode" c_pinMode
    :: Ptr (Ptr (CInt -> CInt -> IO ()))

然后,在调用 'pinMode' 的 haskell 函数的实现中,我使用 peek 两次来获取对底层函数的引用。但是我不断收到编译错误,编译器告诉我(CInt -> CInt -> IO ()) 类型的函数不是“可存储”类型类的实例。

所以我检查了可存储类型类,以使 (CInt -> CInt -> IO ()) 成为可存储类型类的实例。所需的最低实现是 peek、poke 和其他一些功能。我意识到,它真的不应该这样很难调用被指针引用的函数..

我觉得我缺少一些基本的东西。有人可以指出我正确的方向吗?

感谢和问候

【问题讨论】:

  • extern void (*pinMode) (int pin, int mode);pinMode 声明为一个指针,指向一个接受两个ints 并返回void 的函数。试试foreign import ccall unsafe "wiringPi.h pinMode" c_pinMode,获取指针的地址会给你太多的间接层级(但我对 FFI 不太熟悉,所以我不能 100% 确定我的解释是正确的)。
  • pinMode 是函数还是函数指针?如果是函数指针,我觉得foreign import ccall unsafe "wiringPi.h &pinMode" c_pinMode的类型应该是Ptr (FunPtr (CInt -> CInt -> IO ()))
  • @nymk pinMode 是一个指向以 2 个整数作为参数的过程的指针。说,我将导入的类型重写为您提到的内容。是否仍然可以在 haskell 端获得对底层函数的引用?因为,如果 c_pinMode 的类型是Ptr (FunPtr (CInt -> CInt -> IO ())),我不能做c_pinMode (3 :: CInt) (4 :: CInt)。我认为这行不通。不过我会试试你说的。

标签: haskell ffi


【解决方案1】:

假设我们在 foo.c 中定义了一个 C 函数指针。

void foo(int x, int y)
{
    printf("foo: sum = %d\n", x+y);
}

typedef void (*FooPtr) (int, int);
FooPtr fooptr = foo;

为了调用fooptr指向的函数,我们不仅需要声明静态地址导入,还需要声明动态导入。动态存根可以帮助我们将FunPtr 值转换为对应的Haskell 函数。

type Foo = CInt -> CInt -> IO ()

foreign import ccall "foo.c &fooptr" fooptr :: Ptr (FunPtr Foo)
foreign import ccall "dynamic" mkFooFun :: FunPtr Foo -> Foo

main = do
    funcptr <- peek fooptr
    mkFooFun funcptr 1 2

fooptr 是一个指向外部函数的导入地址。它的类型既不是Ptr (Ptr a)也不是FunPtr a

如果我们导入foo的地址,它的类型将是FunPtr Foo。为了使用它,我们仍然需要mkFooFun的帮助。

foreign import ccall "foo.c &foo" fooptr2 :: FunPtr Foo
main = mkFooFun fooptr2 1 2

在这个例子中,由于我们可以访问foo,所以最简单的调用foo的方法是

foreign import ccall "foo.c foo" foo :: Foo
main = foo 1 2

【讨论】:

  • 感谢您提供非常全面的回答。实际上,在几分钟前我自己发现了如何做之后,我回到这里发布了相同的解决方案。但是通过您的回答,我更全面地了解了它。最重要的是,fooptr 只是一个导入的内存地址。
猜你喜欢
  • 1970-01-01
  • 2013-03-31
  • 2013-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多