c# haskell ffi

Llamando a Haskell desde C#



ffi (2)

Como referencia, pude obtener el siguiente procedimiento para trabajar en Windows ...

{-# LANGUAGE ForeignFunctionInterface #-} module Fibonacci () where import Data.Word import Foreign.C.Types fibs :: [Word32] fibs = 1 : 1 : zipWith (+) fibs (tail fibs) fibonacci :: Word8 -> Word32 fibonacci n = if n > 47 then 0 else fibs !! (fromIntegral n) c_fibonacci :: CUChar -> CUInt c_fibonacci (CUChar n) = CUInt (fibonacci n) foreign export ccall c_fibonacci :: CUChar -> CUInt

Compila esto con

ghc --make -shared Fibonacci.hs

Esto produce media docena de archivos, uno de los cuales es HSdll.dll . Luego copié eso en un proyecto de Visual Studio C # e hice lo siguiente:

using System; using System.Runtime.InteropServices; namespace ConsoleApplication1 { public sealed class Fibonacci : IDisposable { #region DLL imports [DllImport("HSdll.dll", CallingConvention=CallingConvention.Cdecl)] private static extern unsafe void hs_init(IntPtr argc, IntPtr argv); [DllImport("HSdll.dll", CallingConvention = CallingConvention.Cdecl)] private static extern unsafe void hs_exit(); [DllImport("HSdll.dll", CallingConvention = CallingConvention.Cdecl)] private static extern UInt32 c_fibonacci(byte i); #endregion #region Public interface public Fibonacci() { Console.WriteLine("Initialising DLL..."); unsafe { hs_init(IntPtr.Zero, IntPtr.Zero); } } public void Dispose() { Console.WriteLine("Shutting down DLL..."); unsafe { hs_exit(); } } public UInt32 fibonacci(byte i) { Console.WriteLine(string.Format("Calling c_fibonacci({0})...", i)); var result = c_fibonacci(i); Console.WriteLine(string.Format("Result = {0}", result)); return result; } #endregion } }

Las llamadas Console.WriteLine() son obviamente opcionales.

No he intentado ejecutar esto bajo Mono / Linux todavía, pero es presumiblemente similar.

En resumen, es aproximadamente la misma dificultad que obtener una DLL de C ++ para funcionar. (Es decir, lograr que las firmas de tipo coincidan y hacer que las referencias funcionen correctamente es lo difícil).

También tuve que editar la configuración del proyecto y seleccionar "permitir código no seguro".

Acabo de pasar la última semana descubriendo cómo ejecutar código C ++ desde C # como parte de mi trabajo diario. Nos llevó mucho tiempo resolverlo, pero la solución final es bastante simple.

Ahora tengo curiosidad ... ¿Qué tan difícil sería llamar a Haskell desde C #? (Nota: esto es llamar a Haskell desde C #, no al revés, entonces el ejecutable principal es C #).

Si es realmente difícil, no me molestaré. Pero si es razonablemente fácil, podría tener que jugar con eso ...

Básicamente, escribimos algunos códigos C ++. En Windows se compila en una DLL, en Linux se compila en un objeto compartido ( *.so ). Luego, en el lado C #, usted hace una DllImport y escribe un código de administración de memoria manual si está tratando de pasar algo que no sea trivial. (Por ejemplo, matrices, cadenas, etc.)

Sé que se supone que GHC admite la construcción de bibliotecas compartidas en ambas plataformas, pero no estoy seguro de los detalles técnicos. ¿Cuál es la sintaxis para exportar cosas, y la persona que llama tiene que hacer algo especial para inicializar primero la DLL?

Para ser concretos: supongamos que existe una función foobar :: FilePath -> IO Int32 . ¿Alguien puede armar un pequeño boceto que muestre:

  • Qué declaraciones de Haskell necesito escribir para exponer esto al mundo exterior.
  • ¿Cómo le digo a GHC que cree un archivo DLL / SO autónomo?
  • Cualquier cosa especial que la persona que llama tenga que hacer, más allá del proceso habitual de vinculación de foobar .

No estoy demasiado preocupado por la sintaxis real para el lado C #; Creo que lo he desconcertado más o menos.

PD. Miré brevemente hs-dotnet , pero esto parece ser específico de Windows. (Es decir, no funcionará con Mono, por lo que no funcionará en Linux).


En lo que respecta a ambos idiomas, básicamente puede pretender que está tratando de interactuar con el código C.

Este es un tema complejo, así que en lugar de tratar de explicarlo todo, me concentraré en hacer un ejemplo simple sobre el que pueda construir utilizando los recursos que se relacionan a continuación.

  1. Primero, necesita escribir wrappers para sus funciones Haskell que usan tipos de los módulos Foreign.C.* lugar de los tipos habituales de haskell. CInt lugar de Int , CString lugar de String , etc. Este es el paso más complicado, especialmente cuando tiene que tratar con tipos definidos por el usuario.

    También debe escribir declaraciones de foreign export para esas funciones utilizando la extensión ForeignFunctionInterface .

    {-# LANGUAGE ForeignFunctionInterface #-} module Foo where import Foreign.C.String import Foreign.C.Types foreign export ccall foo :: CString -> IO CInt foo :: CString -> IO CInt foo c_str = do str <- peekCString c_str result <- hs_foo str return $ fromIntegral result hs_foo :: String -> IO Int hs_foo str = do putStrLn $ "Hello, " ++ str return (length str + 42)

  2. Luego, al compilar, le dices a GHC que haga una biblioteca compartida:

    $ ghc -O2 --make -no-hs-main -optl ''-shared'' -o Foo.so Foo.hs

  3. Desde el lado C #, además de importar la función que desea llamar, también debe importar hs_init() y llamarlo para inicializar el sistema de tiempo de ejecución antes de poder llamar a cualquier función Haskell. También debe llamar a hs_exit() cuando haya terminado.

    using System; using System.Runtime.InteropServices; namespace Foo { class MainClass { [DllImport("Foo.so", CallingConvention = CallingConvention.Cdecl)] private static extern void hs_init(IntPtr argc, IntPtr argv); [DllImport("Foo.so", CallingConvention = CallingConvention.Cdecl)] private static extern void hs_exit(); [DllImport("Foo.so", CallingConvention = CallingConvention.Cdecl)] private static extern int foo(string str); public static void Main(string[] args) { Console.WriteLine("Initializing runtime..."); hs_init(IntPtr.Zero, IntPtr.Zero); try { Console.WriteLine("Calling to Haskell..."); int result = foo("C#"); Console.WriteLine("Got result: {0}", result); } finally { Console.WriteLine("Exiting runtime..."); hs_exit(); } } } }

  4. Ahora compilamos y ejecutamos:

    $ mcs -unsafe Foo.cs $ LD_LIBRARY_PATH=. mono Foo.exe Initializing runtime... Calling to Haskell... Hello, C# Got result: 44 Exiting runtime...

    ¡Funciona!

Recursos: