带有状态的FFI Haskell回调

带有状态的FFI Haskell回调,haskell,ffi,Haskell,Ffi,我的问题是关于如何编写友好的Haskell接口来模拟可从C代码调用的回调。回调在这里处理(),但是,我相信这个问题比该链接中的示例更复杂 假设我们有C代码,需要回调,标题如下所示: typedef int CallbackType(char* input, char* output, int outputMaxSize, void* userData) int execution(CallbackType* caller); 在这种情况下,函数execution接受一个回调函数,并使用它来处

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

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

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

int execution(CallbackType* caller);
在这种情况下,函数
execution
接受一个回调函数,并使用它来处理新数据,本质上是一个闭包。回调需要一个输入字符串、一个用size
outputMaxSize
分配的输出缓冲区和一个userData指针,该指针可以在回调内部强制转换

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

具体来说,以下是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()类型编写回调。相反,我们希望用MVAR来替换它 感觉更自然。所以我们想写一个函数:

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在很大程度上并不难实现, 转换string->cstring,Int->CInt,并复制输出字符串

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


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

如果您想访问像
MVar
这样的Haskell结构,它没有库函数将其转换为指针表示(这意味着它不应该被传递给C),然后,您需要执行部分函数应用程序

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

现在,您只需将一个指向向量的指针传递给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端,您正在为MVar对象传递
vcint2
指针。

我绝不是FFI专家,但我的理解是,C人员使用了
void*
技巧,因为他们没有真正的闭包。在Haskell中,我们确实有真正的闭包——因此只需将
void*
参数完全从Haskell接口中去掉,并通过部分应用程序关闭任何本地数据(可能是
IORef
MVar
)!明白了。我试试看。我想这可能是装订所做的,但我没有意识到。谢谢你的回复@tigger,我做了DanielWagner之前建议的从C同步回调Haskell的相同技巧-通过应用MVar参数获取部分函数,并让C函数使用MVar的数据回调它。如果您的MVar更复杂,那么您可以使用可存储向量或可存储实例将数据从C传递到MVar。将Ptr传递到可存储实例到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 ()
/** Haskell Storable Vector element with two int members **/
typedef struct vcint2{
  int a;
  int b;
} vcint2;