While your way works, it's worth noting that the dificulties you encountered were of your own doing unfortunately (and not a bug in GHC) :( (the following assumes you used the ghc documentation when building the dll and have your RTS loading in dll main).
for the first part, the memory allocation issues you present, there's a much easier C# native way of handling this. which is unsafe code. any memor allocated in unsafe code will be allocated outside the managed heap. So this would negate the need for C trickery.
The second part if the use of the loadLibrary in C#. The reason P/Invoke can't find your export is quite simple: in your haskell code you declared the export statement using ccall, while in .NET the standard naming convention is stdcall, which is also the standard for Win32 Api calls.
stdcall and ccall have different name manglings and resposibilities in term of argument cleanup.
in particular, GHC/GCC will have exported "wEval" while .NET by default would be looking for "_wEval@4". Now that's quite easy to fix, just add CallingConvention = CallingConvention.Cdecl.
but using this calling convention the caller needs to clean up the stack. So you would need extra work. Now assuming you're only going to use this on windows, just export your haskell function as stdcall. This makes your .net code simpler and makes
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
public static extern string myExportedFunction(string in);
almost correct.
what's correct would be for example
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
public unsafe static extern char* myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in);
no more need for loadLibrary or the like. and to get a manage string just use
String result = new String(myExportedFunction("hello"));
for instance.
In theory
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
[return : MarshalAs(UnmanagedType.LPWStr)]
public static extern string myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in);
should work too, but I've had problems with this in the past.
if you want to stay completely in managed land, you could always do
[DllImport("foo.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr myExportedFunction([MarshalAs(UnmanagedType.LPWStr)]string in);
and then it can be used as
string result = Marshal.PtrToStringAnsi(myExportedFunction("hello"));
Cheers,
Phyx