2012-02-14 24 views
7

Mi pregunta es sobre cómo escribir amigables interfaces Haskell que modelan devoluciones de llamada que se pueden invocar desde el código C. Las retrollamadas se tratan aquí (HaskellWiki), sin embargo, creo que esta pregunta es más compleja que el ejemplo de ese enlace.Rellamada de FFI Haskell con estado

Supongamos que tenemos el código C, lo que requiere devoluciones de llamada y la cabecera tiene el siguiente aspecto:

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

int execution(CallbackType* caller); 

En este caso la función execution toma una función de devolución de llamada y usar eso para procesar nuevos datos, esencialmente un cierre. La devolución de llamada espera una cadena de entrada, un búfer de salida que se ha asignado con el tamaño outputMaxSize y el puntero userData, que se puede convertir, sin embargo, dentro de la devolución de llamada.

Hacemos cosas similares en Haskell, cuando pasamos los cierres con MVars, por lo que aún podemos comunicarnos. Por lo tanto, cuando escribimos la interfaz Foreign, nos gustaría mantener este tipo de tipo.

Específicamente aquí es lo que el Código FFI podría ser:

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 

Los usuarios deben ser capaces de hacer este tipo de cosas, pero se siente como una interfaz pobres ya que necesitan para escribir devoluciones de llamada con el tipo Ptr(). Más bien, nos gustaría reemplazar esto con MVars que se sienten más naturales. Así que nos gustaría escribir una función:

myCallback :: String -> Int -> MVar a -> (Int, String) 
myCallback input maxOutLength data = ... 

el fin de convertir a C, nos gustaría tener una función como:

castCallback :: (String -> Int -> MVar a -> (Int, String)) 
      -> (CString -> CString -> CInt -> Ptr() -> IO CInt) 

main = wrap_callBack (castCallback myCallback) >>= execute 

En este caso castCallback es en su mayor parte no es difícil de implementar, convert string -> cstring, Int -> CInt, y copia sobre la cadena de salida.

La parte difícil sin embargo está resolviendo el MVar a Ptr, que no es necesariamente almacenable.

Mi pregunta es cuál es la mejor manera de escribir un código de devolución de llamada en Haskell, que aún se puede comunicar con.

+0

No soy de ninguna manera un experto en FFI, pero mi entendimiento es que los chicos de C usaban el truco 'void *' porque no tienen cierres reales. En Haskell, tenemos cierres reales, así que simplemente deje el argumento 'void *' fuera de la interfaz Haskell por completo y cierre todos los datos locales (quizás un 'IORef' o' MVar') a través de una aplicación parcial. –

+0

¡Ahh! Gotcha. Lo probaré. Creo que eso es lo que el enlace pudo haber estado haciendo, pero no entendí eso. ¡Gracias por la respuesta! –

+0

@tigger, hice el mismo truco antes de que DanielWagner sugirió una devolución de llamada sincronizada en Haskell desde C: obtenga una función parcial al aplicar el argumento MVar y deje que la función C vuelva a llamarlo con los datos de MVar. Si su MVar es más complicado, puede usar un vector almacenable o una instancia almacenable para pasar los datos a MVar desde C. Pase un Ptr a instancia almacenable a C. Un ejemplo aquí: http://hpaste.org/63702 – Sal

Respuesta

9

Si desea acceder a una estructura Haskell como MVar que no tiene una función de biblioteca para convertirla en una representación de puntero (lo que significa que no se debe pasar a C), debe realizar una función parcial de aplicación .

En la aplicación de función parcial, el truco consiste en crear una función parcial con MVar ya aplicado, y pasar el puntero a esa función a C. C lo devolverá con el objeto para poner en MVar. Un código de ejemplo a continuación (todo el código de abajo se deriva de algo que hice antes - lo modifico para ejemplos aquí, pero no han probado las modificaciones):

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

Qué pasa si su objeto MVar es más complejo? Entonces necesitas construir una instancia Almacenable del objeto MVar si no existe.Por ejemplo, si quiero usar un MVar con array de pares de Ints, para definir primero una instancia Storable de pares Int (SV es Storable Vector, MSV es 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 #-} 

Ahora, sólo puede pasar una puntero al vector a C, haga que actualice el vector y devuelva la llamada a la función vacía sin argumentos (ya que C ya está llenando el vector). Esto también evita costosos de clasificación de datos mediante el intercambio de memoria entre Haskell y 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() 

Por el lado C, necesitará una declaración de struct VCInt2 para que sepa cómo analizar es:

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

Así , en el lado C, lo está pasando vcint2 puntero para el objeto MVar.

Cuestiones relacionadas