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.
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#? –
Bene, ho ottenuto un intero programma scritto in haskell, la funzione è solo un'interfaccia 'semplice', quindi non può davvero essere riscritta in C# –
La 'H' in Haskell non è silenziosa. Pertanto dovresti usare l'articolo appropriato 'a' su 'an'. –