あなたの方法はうまくいきますが、あなたが遭遇した問題は残念ながらあなた自身の行動によるものであることに注意してください ( GHCのバグではありません) :( (以下では、DLL をビルドするときに GHC ドキュメントを使用し、RTSを DLL main にロードしていると想定しています) )。
最初の部分、あなたが提示するメモリ割り当ての問題については、これを処理するはるかに簡単な C# ネイティブの方法があります。これは安全でないコードです。アンセーフ コードで割り当てられたメモリは、マネージ ヒープの外部に割り当てられます。したがって、これにより、C のトリックの必要性がなくなります。
2 番目の部分は、C# での LoadLibrary の使用です。P/Invokeがエクスポートを見つけられない理由は非常に単純です。Haskell コードでは を使用してエクスポート ステートメントを宣言しましたがccall、.NET では標準の命名規則はであり、これはWin32 API 呼び出しstdcallの標準でもあります。
stdcall引数のクリーンアップに関して、ccall異なる名前マングリングと責任があります。
特に、GHC/GCC は "wEval" をエクスポートしますが、.NET はデフォルトで "_wEval@4" を探します。CallingConvention = CallingConvention.Cdecl を追加するだけで、簡単に修正できます。
ただし、この呼び出し規約を使用すると、呼び出し元はスタックをクリーンアップする必要があります。したがって、追加の作業が必要になります。これを Windows でのみ使用すると仮定すると、Haskell 関数を としてエクスポートするだけstdcallです。これにより、.NET コードが単純になり、
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
public static extern string myExportedFunction(string in);
ほぼ正しい。
正しいのは例えば
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
public unsafe static extern char* myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in);
loadLibrary などはもう必要ありません。管理された文字列を取得するには、次を使用します
String result = new String(myExportedFunction("hello"));
例えば。
と思うだろう
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
[return : MarshalAs(UnmanagedType.LPWStr)]
public static extern string myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in);
も機能するはずですが、マーシャラーは文字列が CoTaskMemAlloc で割り当てられていると想定し、その上で CoTaskMemFree を呼び出してクラッシュするため、機能しません。
管理された土地に完全にとどまりたい場合は、いつでも行うことができます
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in);
そして、それは次のように使用できます
string result = Marshal.PtrToStringUni(myExportedFunction("hello"));
ツールはここから入手できますhttp://hackage.haskell.org/package/Hs2lib-0.4.8
更新: 最近発見した大きな落とし穴があります。.NET の String 型は不変であることを覚えておく必要があります。したがって、マーシャラーがそれを Haskell コードに送信すると、そこで得られる CWString は元のコピーです。これを解放しなければなりません。GC が C# で実行される場合、コピーである CWString には影響しません。
ただし問題は、Haskell コードでこれを解放すると、freeCWString を使用できないことです。ポインターは、C (msvcrt.dll) の割り当てでは割り当てられませんでした。これを解決するには、(私が知っている) 3 つの方法があります。
- Haskell 関数を呼び出すときは、C# コードで String の代わりに char* を使用します。次に、 return を呼び出すときに解放するポインターを取得するか、 fixedを使用してポインターを初期化します。
- Haskell でCoTaskMemFreeをインポートし、Haskell でポインターを解放します
- String の代わりに StringBuilder を使用します。これについては完全にはわかりませんが、StringBuilder はネイティブ ポインターとして実装されているため、マーシャラーはこのポインターを Haskell コードに渡すだけです (更新も可能です)。呼び出しが返された後に GC が実行されると、StringBuilder が解放されます。