Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FFI Haskell Callback with State

Tags:

haskell

ffi

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.

like image 665
Anil Vaitla Avatar asked Feb 14 '12 20:02

Anil Vaitla


1 Answers

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):

-- 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

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 is Storable Vector, MSV is Storable 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 #-}

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.

-- 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 ()

On C side, you will need a struct declaration for VCInt2 so that it knows how to parse it:

/** Haskell Storable Vector element with two int members **/
typedef struct vcint2{
  int a;
  int b;
} vcint2;

So, on C side, you are passing it vcint2 pointer for MVar object.

like image 138
Sal Avatar answered Sep 27 '22 20:09

Sal