gpt4 book ai didi

haskell - 带状态的 FFI Haskell 回调

转载 作者:行者123 更新时间:2023-12-03 15:02:23 24 4
gpt4 key购买 nike

我的问题是关于如何编写可以从 C 代码调用的回调模型的友好 Haskell 接口(interface)。回调在此处解决(HaskellWiki),但是,我相信这个问题比该链接中的示例更复杂。

假设我们有 C 代码,需要回调,并且 header 如下所示:

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

int execution(CallbackType* caller);

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

我们在 haskell 中做类似的事情,当我们用 MVar 传递闭包时,我们仍然可以通信。因此,当我们编写 Foreign 接口(interface)时,我们希望保留这种类型。

具体来说,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 中编写回调代码的最佳方法是什么,仍然可以与之通信。

最佳答案

如果你想访问像 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 VectorMSVStorable 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 #-}

现在,您只需将指向向量的指针传递给 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 ()

在 C 端,您将需要 VCInt2 的结构声明,以便它知道如何解析它:
/** Haskell Storable Vector element with two int members **/
typedef struct vcint2{
int a;
int b;
} vcint2;

所以,在 C 端,你正在传递它 vcint2 MVar 对象的指针。

关于haskell - 带状态的 FFI Haskell 回调,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9283528/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com