FFI Haskell回调状态

时间:2012-02-14 20:12:50

标签: haskell ffi

我的问题是如何编写友好的Haskell接口来模拟可以从C代码调用的回调。这里回答了回调(HaskellWiki),但是,我认为这个问题比该链接中的示例更复杂。

假设我们有C代码,需要回调,标题如下所示:

typedef int CallbackType(char* input, char* output, int outputMaxSize, void* userData)

int execution(CallbackType* caller);

在这种情况下,函数execution采用回调函数,并将使用它来处理新数据,本质上是一个闭包。回调需要一个输入字符串,一个已分配大小为outputMaxSize的输出缓冲区和userData指针,但这些指针可以在回调中进行转换。

我们在haskell中做类似的事情,当我们使用MVars传递闭包时,我们仍然可以进行通信。因此,当我们编写外部接口时,我们希望保留这种类型。

具体来说,这就是FFI代码的样子:

type Callback = CString -> CString -> CInt -> Ptr () -> IO CInt

foreign import ccall safe "wrapper"
    wrap_callBack :: Callback -> IO (FunPtr Callback)

foreign import ccall safe "execution"
    execute :: FunPtr Callback -> IO CInt 

用户应该能够做到这一点,但从那时起感觉就像一个糟糕的界面 他们需要编写类型为Ptr()的回调。相反,我们想用MVars替换它 感觉更自然。所以我们想写一个函数:

myCallback :: String -> Int -> MVar a -> (Int, String)
myCallback input maxOutLength data = ...

为了转换为C,我们希望有一个像:

这样的函数
castCallback :: ( String -> Int -> MVar a -> (Int, String) )
             -> ( CString -> CString -> CInt -> Ptr () -> IO CInt )

main = wrap_callBack (castCallback myCallback) >>= execute

在这种情况下,castCallback在很大程度上并不难实现, 转换字符串 - > cstring,Int - > CInt,并复制输出字符串。

然而,困难的部分是将MVar解析为Ptr,这不一定是可存储的。

我的问题是在Haskell中编写回调代码的最佳方法是什么,仍然可以与之沟通。

1 个答案:

答案 0 :(得分:10)

如果你想访问像MVar那样没有库函数的Haskell结构将它转换为指针表示(意味着它不应该传递给C),那么你需要做部分功能应用。

在部分函数应用程序中,诀窍是使用已经应用的MVar构建部分函数,​​并将指向该函数的指针传递给C.然后,C将使用要放入MVar的对象调用它。下面是一个示例代码(下面的所有代码都是从我之前做过的事情中得到的 - 我在这里修改了它的示例,但没有测试修改):

-- this is the function that C will call back
syncWithC :: MVar CInt -> CInt -> IO () 
syncWithC m x = do 
              putMVar m x
              return ()

foreign import ccall "wrapper"
  syncWithCWrap :: (CInt -> IO ()) -> IO (FunPtr (CInt  -> IO ()))

main = do
    m <- newEmptyMVar
    -- create a partial function with mvar m already applied. Pass to C. C will back with CInt
    f <- syncWithCWrap $ syncWithC m

如果你的MVar对象更复杂怎么办?然后,如果MVar对象不存在,则需要构建它的Storable实例。例如,如果我想使用具有Ints对的数组的MVar,那么首先定义一对Storable Int对​​实例(SVStorable VectorMSV是{ {1}}):

Storable Mutable Vector

现在,您可以将指向Vector的指针传递给C,让它更新向量,并在没有参数的情况下回调void函数(因为C已经填充了向量)。这也可以通过在Haskell和C之间共享内存来避免昂贵的数据编组。

data VCInt2 = IV2 {-# UNPACK #-} !CInt
                  {-# UNPACK #-} !CInt

instance SV.Storable VCInt2 where
  sizeOf _ = sizeOf (undefined :: CInt) * 2
  alignment _ = alignment (undefined :: CInt)
  peek p = do
             a <- peekElemOff q 0
             b <- peekElemOff q 1
             return (IV2 a b)
    where q = castPtr p
  {-# INLINE peek #-}
  poke p (IV2 a b) = do
             pokeElemOff q 0 a
             pokeElemOff q 1 b
    where q = castPtr p
  {-# INLINE poke #-}

在C方面,你需要一个VCInt2的结构声明,以便它知道如何解析它:

-- a "wrapper" import is a converter for converting a Haskell function to a foreign function pointer
foreign import ccall "wrapper"
  syncWithCWrap :: IO () -> IO (FunPtr (IO ()))


-- call syncWithCWrap on syncWithC with both arguments applied
-- the result is a function with no arguments. Pass the function, and 
-- pointer to x to C. Have C fill in x first, and then call back syncWithC 
-- with no arguments
syncWithC :: MVar (SV.Vector VCInt2) -> MSV.IOVector VCInt2 -> IO ()
syncWithC m1 x = do
              SV.unsafeFreeze x >>= putMVar m1
              return ()

因此,在C方面,您正在为MVar对象传递/** Haskell Storable Vector element with two int members **/ typedef struct vcint2{ int a; int b; } vcint2; 指针。