我的问题是如何编写友好的Haskell接口,以模拟可以从C代码调用的回调。回调在此处进行处理(HaskellWiki),但我认为这个问题比该链接中的示例更复杂。
假设我们有需要回调的C代码,头文件如下:
在这种情况下,函数
我们在Haskell中也会做类似的事情,当我们传递带有MVars的闭包时,我们仍然可以进行通信。因此,在编写外部接口时,我们希望保持这种类型。
具体来说,以下是FFI代码的示例:
为了转换成C语言,我们需要一个如下所示的函数:
```c // 伪代码 int toC(int x) { // 转换逻辑 } ```
在这种情况下,castCallback 的实现大部分不难, 将字符串转换为 cstring,Int 转换为 CInt,并复制输出字符串。
然而,困难的部分是解决 MVar 到 Ptr 的映射,这并不一定可存储。
我的问题是,在 Haskell 中编写回调代码的最佳方法是什么,同时仍然可以进行通信。
假设我们有需要回调的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
用户应该能够完成这种操作,但是使用type Ptr ()编写回调函数的方式显得界面比较差。相反,我们更喜欢使用感觉更自然的MVars来替代它。因此,我们想要编写一个函数:
myCallback :: String -> Int -> MVar a -> (Int, String)
myCallback input maxOutLength data = ...
为了转换成C语言,我们需要一个如下所示的函数:
```c // 伪代码 int toC(int x) { // 转换逻辑 } ```
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 中编写回调代码的最佳方法是什么,同时仍然可以进行通信。
void *
技巧是因为他们没有真正的闭包。在Haskell中,我们有真正的闭包--所以只需完全忽略Haskell接口中的void *
参数,并通过部分应用封闭任何本地数据(例如IORef
或MVar
)。 - Daniel Wagner