我的问题是如何编写友好的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中编写回调代码的最佳方法是什么,仍然可以与之沟通。
答案 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对实例(SV
是Storable Vector
,MSV
是{ {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;
指针。