我的问题是如何编写友好的Haskell接口来模拟可以从C代码调用的回调.这里回答了回调(HaskellWiki),但是,我认为这个问题比该链接的例子更复杂.
假设我们有C代码,需要回调,标题如下所示:
typedef int CallbackType(char* input, char* output, int outputMaxSize, void* userData)
int execution(CallbackType* caller);
Run Code Online (Sandbox Code Playgroud)
在这种情况下,该函数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
Run Code Online (Sandbox Code Playgroud)
用户应该可以做这种事情,但感觉就像一个糟糕的接口,因为他们需要编写类型为Ptr()的回调.相反,我们想用感觉更自然的MVars来代替它.所以我们想写一个函数:
myCallback :: String -> Int -> MVar a -> (Int, String)
myCallback input maxOutLength data = ...
Run Code Online (Sandbox Code Playgroud)
为了转换为C,我们希望有一个类似的函数:
castCallback :: ( String -> Int -> MVar a -> (Int, String) )
-> ( CString -> CString -> CInt -> Ptr () -> IO CInt )
main = wrap_callBack (castCallback myCallback) >>= execute
Run Code Online (Sandbox Code Playgroud)
在这种情况下,castCallback在很大程度上不难实现,转换string - > cstring,Int - > CInt,并复制输出字符串.
然而,困难的部分是将MVar解析为Ptr,这不一定是可存储的.
我的问题是在Haskell中编写回调代码的最佳方法是什么,它仍然可以与之通信.
Sal*_*Sal 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
Run Code Online (Sandbox Code Playgroud)
如果你的MVar对象更复杂怎么办?然后,如果MVar对象不存在,则需要构建它的Storable实例.例如,如果我想使用具有Ints对的数组的MVar,那么首先定义StorableInt对的实例(SVis Storable Vector,MSVis Storable Mutable Vector):
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 #-}
Run Code Online (Sandbox Code Playgroud)
现在,您可以将指向Vector的指针传递给C,让它更新向量,并回调没有参数的void函数(因为C已经填充了向量).这也通过在Haskell和C之间共享内存来避免昂贵的数据编组.
-- 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 ()
Run Code Online (Sandbox Code Playgroud)
在C端,您将需要VCInt2的结构声明,以便它知道如何解析它:
/** Haskell Storable Vector element with two int members **/
typedef struct vcint2{
int a;
int b;
} vcint2;
Run Code Online (Sandbox Code Playgroud)
因此,在C端,您传递的vcint2是MVar对象的指针.