Hay dos cosas que puede hacer con las funciones relacionadas con el FFI: 1) Marcado: esto significa convertir una función en un tipo que se puede exportar a través del FFI. Esto se realiza por FunPtr
. 2) Exportar: esto significa crear un medio para que el código no Haskell llame a una función Haskell.
Su clase FFI ayuda con la clasificación, y primero creo algunas instancias de muestra de cómo ordenar las funciones.
Esto no se ha probado, pero se compila y espero que funcione. En primer lugar, vamos a cambiar ligeramente la clase:
class FFI basic ffitype | basic -> ffitype, ffitype -> basic where
toFFI :: basic -> IO ffitype
fromFFI :: ffitype -> IO basic
freeFFI :: ffitype -> IO()
Este dice que, dado el tipo de bien "básica" o "ffitype", el otro se fija [1]. Esto significa que ya no es posible agrupar dos valores diferentes en el mismo tipo, p. ya no se puede tener ambas cosas
instance FFI Int CInt where
instance FFI Int32 CInt where
La razón de esto es porque freeFFI
no se puede utilizar como usted ha definido ella; no hay forma de determinar qué instancia seleccionar desde solo ffitype. Alternativamente, puede cambiar el tipo a freeFFI :: ffitype -> basic -> IO()
, o (¿mejor?) freeFFI :: ffitype -> IO basic
. Entonces no necesitarías fundeps en absoluto.
La única forma de asignar un FunPtr es con una instrucción de "importación externa", que solo funciona con tipos completamente instanciados. También necesita habilitar la extensión ForeignFunctionInterface
. Como resultado, la función toFFI
, que debería devolver un IO (FunPtr x)
, no puede ser polimórfica sobre los tipos de funciones. En otras palabras, necesitaría esto:
foreign import ccall "wrapper"
mkIntFn :: (Int32 -> Int32) -> IO (FunPtr (Int32 -> Int32))
foreign import ccall "dynamic"
dynIntFn :: FunPtr (Int32 -> Int32) -> (Int32 -> Int32)
instance FFI (Int32 -> Int32) (FunPtr (Int32 -> Int32)) where
toFFI = mkIntFn
fromFFI = return . dynIntFn
freeFFI = freeHaskellFunPtr
para cada tipo de función diferente que desea calcular. También necesita la extensión FlexibleInstances
para esta instancia. Hay algunas restricciones impuestas por el FFI: cada tipo debe ser un tipo foráneo marshallable, y el tipo de devolución de función debe ser un tipo foráneo marshallable o una acción IO que devuelve un tipo foráneo Marshallable.
Para los tipos que no son Marshallable (por ejemplo, Strings) necesita algo un poco más complejo. En primer lugar, dado que el marshalling ocurre en IO, solo puede reunir funciones que den como resultado una acción IO. Si desea reunir funciones puras, p. (Cadena -> Cadena), debe levantarlos a la forma (Cadena -> Cadena IO). [2] Definamos dos ayudantes:
wrapFn :: (FFI a ca, FFI b cb) => (a -> IO b) -> (ca -> IO cb)
wrapFn fn = fromFFI >=> fn >=> toFFI
unwrapFn :: (FFI a ca, FFI b cb) => (ca -> IO cb) -> (a -> IO b)
unwrapFn fn a = bracket (toFFI a) freeFFI (fn >=> fromFFI)
Éstos convierten los tipos de funciones en los valores agrupados apropiados, p. wrapStrFn :: (String -> IO String) -> (CString -> IO CString); wrapStrFn = wrapFn
. Tenga en cuenta que unwrapFn
utiliza "Control.Exception.bracket" para garantizar que el recurso se libere en caso de excepciones. Ignorando esto, puede escribir unwrapFn fn = toFFI >=> fn >=> fromFFI
; ver la similitud con wrapFn.
Ahora que tenemos estos ayudantes podemos empezar a escribir casos:
foreign import ccall "wrapper"
mkStrFn :: (CString -> IO CString) -> IO (FunPtr (CString -> IO CString))
foreign import ccall "dynamic"
dynStrFn :: FunPtr (CString -> IO CString) -> (CString -> IO CString)
instance FFI (String -> IO String) (FunPtr (CString -> IO CString)) where
toFFI = mkStrFn . wrapFn
fromFFI = return . unwrapFn . dynStrFn
freeFFI = freeHaskellFunPtr
Al igual que antes, no es posible realizar estas funciones polimórficas, lo que conduce a mi mayor reserva sobre este sistema. Es una gran sobrecarga porque necesita crear contenedores e instancias separadas para cada tipo de función. A menos que esté organizando muchas funciones, dudaría seriamente de que valga la pena el esfuerzo.
Así es como se pueden ordenar las funciones, pero ¿qué ocurre si se quiere que estén disponibles para llamar al código? Este otro proceso es exportando la función, y ya hemos desarrollado la mayoría de lo necesario.
Las funciones exportadas deben tener tipos Marshallable, al igual que FunPtr
s. Simplemente podemos reutilizar el wrapFn
para hacer esto. Para exportar unas pocas funciones todo lo que tiene que hacer es envolverlos con wrapFn
y exportar las versiones envueltos:
f1 :: Int -> Int
f1 = (+2)
f2 :: String -> String
f2 = reverse
f3 :: String -> IO Int
f3 = return . length
foreign export ccall f1Wrapped :: CInt -> IO CInt
f1Wrapped = wrapFn (return . f1)
foreign export ccall f2Wrapped :: CString -> IO CString
f2Wrapped = wrapFn (return . f2)
foreign export ccall f3Wrapped :: CString -> IO CInt
f3Wrapped = wrapFn f3
Por desgracia, esta configuración sólo funciona para las funciones de un solo argumento. Para apoyar todas las funciones, vamos a hacer otra clase:
class ExportFunction a b where
exportFunction :: a -> b
instance (FFI a ca, FFI b cb) => ExportFunction (a->b) (ca -> IO cb) where
exportFunction fn = (wrapFn (return . fn))
instance (FFI a ca, FFI b cb, FFI d cd) => ExportFunction (a->b->d) (ca->cb->IO cd) where
exportFunction fn = \ca cb -> do
a <- fromFFI ca
b <- fromFFI cb
toFFI $ fn a b
Ahora podemos usar exportFunction
para las funciones de 1 y 2 argumentos:
f4 :: Int -> Int -> Int
f4 = (+)
f4Wrapped :: CInt -> CInt -> IO CInt
f4Wrapped = exportFunction f4
foreign export ccall f4Wrapped :: CInt -> CInt -> IO CInt
f3Wrapped2 = :: CString -> IO CInt
f3Wrapped2 = exportFunction f3
foreign export ccall f3Wrapped2 :: CString -> IO CInt
f3Wrapped2 = exportFunction f3
Ahora sólo tiene que escribir más instancias de ExportFunction
para convertir automáticamente cualquier función al tipo apropiado para exportar. Creo que esto es lo mejor que puede hacer sin usar un tipo de pre-procesador o UnSafePerformIO.
[1] Técnicamente, no creo que haya ninguna necesidad para el fundep "basic -> ffitype", por lo que podría eliminarlo para permitir que un tipo básico se asigne a múltiples ffitypes. Una razón para hacerlo sería mapear todos los tamaños de enteros a enteros, aunque las implementaciones toFFI
serían con pérdida.
[2] Una ligera simplificación. Puede ordenar una función String -> String
en el tipo de FFI CString -> IO CString
. Pero ahora no puede convertir la función CString -> IO CString
a String -> String
debido a IO en el tipo de devolución.
¿Conoces [FunPtr] (http://haskell.org/ghc/docs/6.12.1/html/libraries/base-4.2.0.0/Foreign-Ptr.html#t%3AFunPtr)? De lo contrario, consulte Foreign.Ptr y su documentación relacionada. –
Sí, lo sé. La idea aquí es hacer una conversión automática hacia/desde FFI. Por ejemplo, 'String -> String' se convertiría en' CString -> CString', o algo similar. – Tener
¡No utilice extensiones Haskell en bibliotecas de enlaces FFI! hace que sea muy difícil compilar con otros compiladores Haskell que no son compatibles con las extensiones utilizadas. –