2009-09-29 13 views
14

Desidero utilizzare una funzione Haskell con il seguente tipo: string -> string da un programma C#.Chiama una funzione Haskell in .NET

Desidero utilizzare hs-dotnet per collegare entrambi i mondi. L'autore afferma che è possibile, ma non fornire esempi di questo caso. Gli unici esempi forniti sono quelli da utilizzare .NET di Haskell.

Esiste un esempio di questo utilizzo o come utilizzarlo? (Ho usato .NET Reflector sull'assieme di bridging, ma non ho capito niente.)

+0

Non conosco Haskell, quindi non posso aiutare ma sono curioso di sapere se sarebbe più semplice implementare la funzione Haskell in C#. Questa funzione è così importante e difficile da non poter essere riscritta in C#? –

+0

Bene, ho ottenuto un intero programma scritto in haskell, la funzione è solo un'interfaccia 'semplice', quindi non può davvero essere riscritta in C# –

+3

La 'H' in Haskell non è silenziosa. Pertanto dovresti usare l'articolo appropriato 'a' su 'an'. –

risposta

14

Mentre il modo in cui funziona, vale la pena notare che i dificulties hai incontrato erano del proprio fare, purtroppo (e non un bug in GHC) :((Di seguito si presuppone che hai usato la documentazione GHC quando si genera la DLL e la tua RTS caricamento nella DLL principale)

Per la prima parte, i problemi di allocazione della memoria che si presentano, c'è un modo nativo C# molto più semplice di gestirlo, che è un codice non sicuro. Qualsiasi memoria allocata in codice non sicuro verrà allocata al di fuori della gestione heap. In questo modo si annullerebbe la necessità dell'inganno C.

La seconda parte è l'uso di LoadLibrary in C#. n P/Invoke non riesci a trovare la tua esportazione è abbastanza semplice: nel tuo codice Haskell hai dichiarato la dichiarazione di esportazione usando ccall, mentre in .NET la convenzione di denominazione standard è stdcall, che è anche lo standard per le chiamate API Win32.

stdcall e ccall hanno diverse manomissioni nome e resposabilità in termini di pulizia argomento.

In particolare, GHC/GCC avrà esportato "wEval" mentre .NET di default cercherebbe "_wEval @ 4". Ora è abbastanza facile da risolvere, basta aggiungere CallingConvention = CallingConvention.Cdecl.

Tuttavia, utilizzando questa convenzione di chiamata, il chiamante deve pulire lo stack. Quindi avresti bisogno di un lavoro extra. Ora supponendo che lo userai solo su Windows, esporta semplicemente la tua funzione Haskell come stdcall. Ciò semplifica il tuo codice .NET e rende

[DllImport("foo.dll", CharSet = CharSet.Unicode)] 
public static extern string myExportedFunction(string in); 

quasi corretto.

Qual è corretta sarebbe per esempio

[DllImport("foo.dll", CharSet = CharSet.Unicode)] 
public unsafe static extern char* myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in); 

Non è più necessario per loadLibrary o simili. E per ottenere una stringa gestita basta usare

String result = new String(myExportedFunction("hello")); 

per esempio.

Si potrebbe pensare che

[DllImport("foo.dll", CharSet = CharSet.Unicode)] 
[return : MarshalAs(UnmanagedType.LPWStr)] 
public static extern string myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in); 

dovrebbe funzionare anche, ma non è così in quanto il Marshaller si aspetta che la stringa da sono stati assegnati con CoTaskMemAlloc e chiamerà CoTaskMemFree su di esso e incidente.

Se si vuole rimanere completamente in territorio gestito, si può sempre fare

[DllImport("foo.dll", CharSet = CharSet.Unicode)] 
public static extern IntPtr myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in); 

e quindi può essere utilizzato come

string result = Marshal.PtrToStringUni(myExportedFunction("hello")); 

Tool è disponibile qui http://hackage.haskell.org/package/Hs2lib-0.4.8

Aggiornamento: C'è un po 'di un trucchetto che ho scoperto di recente. Dobbiamo ricordare che il tipo String in .NET è immutabile. Quindi, quando il marshaller lo invia al codice Haskell, il CWString che otteniamo è una copia dell'originale. Noi abbiamo per liberare questo. Quando GC viene eseguito in C# non influirà sul CWString, che è una copia.

Il problema è che quando lo liberiamo nel codice Haskell non possiamo usare freeCWString. Il puntatore non è stato allocato con l'allocazione C (msvcrt.dll). Ci sono tre modi (che io sappia) per risolvere questo.

  • utilizzare char * nel codice C# anziché in String quando si chiama una funzione Haskell. Quindi si ha il puntatore libero quando si chiama restituisce o si inizializza il puntatore utilizzando fixed.
  • importare CoTaskMemFree in Haskell e liberare il puntatore in Haskell
  • utilizzare StringBuilder anziché String. Non sono del tutto sicuro di questo, ma l'idea è che poiché StringBuilder è implementato come un puntatore nativo, Marshaller passa semplicemente questo puntatore al tuo codice Haskell (che può anche aggiornarlo btw). Quando GC viene eseguito dopo il richiamo della chiamata, è necessario liberare StringBuilder.
+0

Grazie, è meglio se posso farlo nel modo giusto. Immagino di essere stato annegato nella documentazione ... –

2

Si può certamente chiamare Haskell da C almeno - si usa "esportazione esterna" nel file Haskell e GHC genera un C intestazione che puoi quindi importare e utilizzare per chiamare in Haskell da C.

Non ho visto questo fatto per i binding .NET - quindi penso che sia meglio chiedere sia all'autore - Sigbjorn - sia a haskell -cafe @ per esempi.

4

Proprio come un aggiornamento, ho risolto il problema creando una DLL haskell e collegando i due mondi in questo modo.

Se si desidera utilizzare lo stesso percorso, assicurarsi di utilizzare ::CoTaskMemAlloc per allocare i dati per il mondo .net. Un altro trucco è l'uso di LoadLibrary/GetProcAdress, per qualche motivo sconosciuto, le importazioni non funzionano automaticamente come dovrebbero essere. Un altro in depth article per aiutare a chiamare haskell da .net.

-11

Se si desidera Haskell su .NET, utilizzare solo F#.