带状态的 FFI Haskell 回调
我的问题是关于如何编写友好的 Haskell 接口来模拟可以从 C 代码调用的回调。回调在这里解决(HaskellWiki),但是,我相信这个问题比该链接中的示例更复杂。
假设我们有需要回调的 C 代码,并且标头如下所示:
typedef int CallbackType(char* input, char* output, int outputMaxSize, void* userData)
int execution(CallbackType* caller);
在这种情况下,函数 execution
接受一个回调函数,并将使用它来处理新数据,本质上是一个闭包。回调需要一个输入字符串、一个已分配大小 outputMaxSize
的输出缓冲区和 userData 指针,但可以在回调内部进行强制转换。
当我们使用 MVar 传递闭包时,我们在 haskell 中做了类似的事情,因此我们仍然可以进行通信。因此,当我们编写Foreign接口时,我们希望保留这种类型。
具体来说,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 在大多数情况下并不难实现, 转换字符串 -> cstring,Int -> CInt,并复制输出字符串。
然而,困难的部分是将 MVar 解析为 Ptr,这不一定是可存储的。
我的问题是在 Haskell 中编写回调代码的最佳方法是什么,并且仍然可以与之通信。
My question is about how to write friendly Haskell Interfaces that model callbacks which can be invoked from C code. Callbacks are addressed here (HaskellWiki), however, I believe this question is more complex than the example from that link.
Suppose we have C code, requiring callbacks and the header looks like the following:
typedef int CallbackType(char* input, char* output, int outputMaxSize, void* userData)
int execution(CallbackType* caller);
In this case the function execution
takes a callback function and will use that to process new data, essentially a closure. The call back expects an input string, an output buffer which has been allocated with size outputMaxSize
and the userData pointer, which can be casted however inside the callback.
We do similar things in haskell, when we pass around closures with MVars, so we can still communicate. Therefore when we write the Foreign interface, we'd like to keep this sort of type.
Specifically here is what the FFI Code might look like:
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
Users should be able to do this sort of thing, but it feels like a poor interface since
they need to write callbacks with type Ptr (). Rather we'd like to replace this with MVars
which feel more natural. So we'd like to write a function:
myCallback :: String -> Int -> MVar a -> (Int, String)
myCallback input maxOutLength data = ...
In order to convert to C, we'd like to have a function like:
castCallback :: ( String -> Int -> MVar a -> (Int, String) )
-> ( CString -> CString -> CInt -> Ptr () -> IO CInt )
main = wrap_callBack (castCallback myCallback) >>= execute
In this case castCallback is for the most part not hard to implement,
convert string -> cstring, Int -> CInt, and copy over the output string.
The hard part however is resolving the MVar to Ptr, which is not necessarily storable.
My Question is what is the best way to go about writing callback code in Haskell, which can still be communicated with.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如果你想访问像 MVar 这样的 Haskell 结构,它没有库函数将其转换为指针表示(意味着它不应该传递给 C),那么你需要这样做部分函数应用。
在部分函数应用程序中,技巧是构建一个已应用 MVar 的部分函数,并将指向该函数的指针传递给 C。然后,C 将使用要放入 MVar 的对象回调它。下面的示例代码(下面的所有代码都源自我之前所做的事情 - 我在这里修改了它作为示例,但尚未测试修改):
如果您的 MVar 对象更复杂怎么办?然后,如果 MVar 对象不存在,则需要构建一个 Storable 实例。例如,如果我想使用具有 Int 对数组的 MVar,则首先定义 Int 对的
Storable
实例(SV
是Storable Vector
code>,MSV
是Storable Mutable Vector
):现在,您只需将指向向量的指针传递给 C,让它更新向量,然后回调 void 函数没有参数(因为 C 已经是填充向量)。这还可以通过在 Haskell 和 C 之间共享内存来避免昂贵的数据编组。
在 C 端,您将需要 VCInt2 的结构声明,以便它知道如何解析它:
因此,在 C 端,您将传递它
vcint2< /code> MVar 对象的指针。
If you want to access a Haskell structure like
MVar
which doesn't have a library function to convert it to a pointer representation (meaning it is not supposed to be passed to C), then you need to do partial function application.In the partial function application, the trick is to build a partial function with MVar already applied, and pass the pointer to that function to C. C will then call it back with the object to put in MVar. An example code below (all the code below is derived from something I did before - I modified it for examples here but haven't tested the modifications):
What if your MVar object is more complex? Then you need to build a Storable instance of the MVar object if it doesn't exist. For example, if I want to use an MVar with array of pair of Ints, then first define a
Storable
instance of Int pairs (SV
isStorable Vector
,MSV
isStorable Mutable Vector
):Now, you can just pass a pointer to the vector to C, have it update the vector, and call back the void function with no arguments (since C is already filling the vector). This also avoid expensive data marshalling by sharing memory between Haskell and C.
On C side, you will need a struct declaration for VCInt2 so that it knows how to parse it:
So, on C side, you are passing it
vcint2
pointer for MVar object.