2012-03-19 29 views
7

Me gustaría crear una aplicación Haskell con .NET gui. Me gustaría utilizar Cabal como mi herramienta de construcción para tomar ventaja de que es la gestión de paquetes, etc. Creo que la parte Haskell debe ser el ejecutable que pone en el código .NET como:Creando una aplicación Haskell con .NET GUI

  1. Esto evita tener que manualmente inicializar el Haskell RTC como se describe aquí: http://www.haskell.org/ghc/docs/7.0.3/html/users_guide/win32-dlls.html

  2. Cabal no puede producir fácilmente archivos DLL de Windows: http://www.haskell.org/haskellwiki/Cabal/Developer-FAQ#Building_DLLs__with_Cabal

he descubierto que es bastante fácil crear una Haskell ejecutable .NET de esa llamada usando hs-dotnet, pero también necesito mi código GUI para volver a llamar a Haskell. Esperaba lograr esto usando el comando de "exportación extranjera" de Haskell, luego llamé a esta función exportada a través de la interoperación nativa de .NET. Sin embargo, la función de "exportación externa" no parece crear un punto de entrada en el ejecutable, no puedo ver el punto de entrada cuando lo hago dumpbin /EXPORTS en el ejecutable resultante. No estoy seguro de si esto se debe a que GHC solo crea puntos de entrada al crear un dll a través del interruptor -shared o si cabal agrega una bandera que suprime la creación del punto de entrada.

Así que supongo que la pregunta es ¿cómo obligo a GHC a crear puntos de entrada en mi ejecutable de Windows? ¿O sería mejor usar un ejecutable .NET siguiendo los pasos necesarios para crear un dll Haskell con cabal e inicializando manualmente el Haskell RTC?

+0

¿De verdad quieres codificar en .net y en haskell lo tomo? Entonces sugeriría que pusieras tu haskell en un dll y llamarías a las funciones de .net. Su punto 1 es fácil de superar e incluso si es bueno, todavía debe usar algo más para construir su parte .net, así que su punto 2 es más una pista (es decir, no lo use) – Jonke

Respuesta

4

Normalmente resuelvo esto pasando devoluciones de llamadas como punteros a funciones. Por ejemplo, tengo una aplicación donde presionar un botón necesita volver a llamar a Haskell (estoy usando Cocoa, pero a excepción de los nombres es muy similar).

Primero, clasifico un objeto NSButton y le doy a mi nueva clase ButtonC un miembro privado del tipo void(*onClickCallback)(). También declaro una función void setClickCallback(ButtonC *button, void(*callback)()); Implemente su subclase para que cada vez que se haga clic en el botón, se llame al puntero a la función. En .Net, puede haber una manera inteligente de hacer esto con los delegados (ha pasado un tiempo desde que usé .Net).

A continuación, mis Haskell miradas de unión de este tipo (algunos códigos omitido):

module Foreign.Button where 

data ButtonObj 
type ButtonPtr = ForeignPtr ButtonObj 

foreign import ccall unsafe "setClickCallback" 
    c_setClickCallback :: Ptr ButtonObj -> FunPtr (IO()) -> IO() 

foreign import ccall "wrapper" 
    makeClickCallback :: IO() -> IO (FunPtr (IO())) 

buttonSetCallback :: ButtonPtr -> FunPtr (IO()) -> IO() 
buttonSetCallback btn cb = 
    withForeignPtr btn $ \p -> c_setClickCallback p cb 

buttonNew :: IO ButtonPtr 
buttonNew = ... 

datos Ahora Haskell de tipo IO() puede ser envuelto en una FunPtr y se pasa a la interfaz gráfica de usuario con buttonSetCallback. Cada vez que se presiona el botón, se lleva a cabo la acción IO().

createButton :: IO ButtonPtr 
createButton = do 
    btn <- buttonNew 
    let buttonClicked = print "The button was clicked!" 
    btnCallback <- makeClickCallback buttonClicked 
    buttonSetCallback btn btnCallback 
    return btn 

Una cosa a tener cuidado de con FunPtr s es que no son basura recogida. Necesitará desasignarlos manualmente cuando haya terminado o tendrá una pérdida de memoria. Una buena práctica es nunca compartir FunPtr s, y tampoco conservar referencias a ellos en el lado de Haskell. De esta forma, su objeto puede liberar el FunPtr como parte de su limpieza. Esto requiere otra devolución de llamada a Haskell (una función freeFunPtr) que debe compartirse entre todos sus objetos y que solo se libera cuando finaliza su ejecutable.

+0

Parece un enfoque razonable, Supongo que una vez que se crea el FunPtr puedes pasarlo a.NET como IntPtr, pero una vez que tienes este puntero a la función en el mundo .NET, ¿cómo lo invocas? – Robert

+1

@Robert: puede enviarlo a un delegado y luego invocar al delegado. Detalles en: http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.getdelegateforfunctionpointer.aspx –

+0

Sonidos, ¡gracias! – Robert

Cuestiones relacionadas