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.
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. –
¡Ahh! Gotcha. Lo probaré. Creo que eso es lo que el enlace pudo haber estado haciendo, pero no entendí eso. ¡Gracias por la respuesta! –
@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