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.
void *
trick because they don't have real closures. In Haskell, we do have real closures -- so just leave thevoid *
argument out of the Haskell interface entirely and close over any local data (perhaps anIORef
orMVar
) via partial application. – Daniel Wagner