FFI Haskell回调状态

Ani*_*tla 7 haskell ffi

我的问题是如何编写友好的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对象的指针.