.net using an Haskell DLL

The previous post on Eq! shown an application using a bridge between two worlds : Haskell and .net. While there exists library to use .net objects from Haskell (like hs-dotnet), there is no documentation on how to use Haskell from .net.

An obvious and seemingly easy solution is to go to the Dll route, GHC can make those on windows, .net is good at importing functions in dll. An easy trip in perspective!

The Haskell side

Ok, we assume that we want a function that take string and output string. As Haskell store characters as Unicode code point and .net is good at Unicode, it’s better to stay in Unicode in the whole pipeline. So here’s the haskell code :

wDoForeign :: (String -> String) -> CWString -> IO CWString
wDoForeign f inCode = do
    inString <- peekCWString inCode
    newCWString $ f inString

wEval :: CWString -> IO CWString wEval = wDoForeign evalFoo

foreign export ccall "wEval" eqWEval :: CWString -> IO CWString

wDoForeign is just an helper function to help Marshaling/UnMarshalling the data, wEval wrap the function we really want to expose : evalFoo.

Compiling this code yield to the creation to a stub file containing a function wEval, which can be called in C or C++. I won’t detail how to create the rest of the DLL, as it’s explained in the GHC User’s Guide.

Now, let’s focus on the specifics difference to make if the Dll must be called by .net :

wchar_t*    dotNetizeW( /out/ wchar_t cHeapString )
{
    size_t length = wcslen( cHeapString );
    wchar_t *out = static_cast>(::CoTaskMemAlloc( (length + 1) * sizeof( wchar_t ) ));

for (int i = 0; i < length; ++i)
    out[i] = cHeapString[i];
out[ length ] = 0;
eqFreeHaskellString( cHeapString );

return out;

}

attribute((dllexport)) wchar_t* myExportedFunction( wchar_t in ) { return dotNetizeW( (wchar_t)wEval( (HsPtr)in ) ); }

Yes it’s a bit complex, here eqFreeHaskellString, is an exported haskell function which call free. Documentation says I could use the normal C free, but in case of change, it’s safe. The problem is you can’t give simply malloc’ed data to .net. It use another heap, different from the C one, and this is the role of ::CoTaskMemAlloc to allocate data in the good heap. If you don’t do this, the function will crash on return. CoTaskMemAlloc is provided by the Ole32 library, so you must have a copy of it around to link against it.

Ok now the DLL is ready, there’s only the easy part on .net.

The .net side of things

In .net we want to use the DllImport attribute to neatly map haskell function. It’s easy, two lines of code for each function (don’t forget the begin & end runtime you must provide in the DLL (see GHC user guide).


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

Right? WRONG

It was too easy isn’t it?
Shit happens here.

For an unknown reason you can’t simply load the library this way. After some tests in C++, the loader don’t seem to find the imports in the DLL, the only way to do it seems to use the LoadLibrary and GetProcAdress to retrieve the functions adress manually and then call it. It’s pretty straightforward in C++, but not so in .net, so here’s a sample :

using System.Runtime.InteropServices;
/* other code blah blah blah */
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string fileName);

[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

[return: MarshalAs(UnmanagedType.LPWStr)] private delegate String Evaluator([MarshalAs(UnmanagedType.LPWStr)][In] String input);

Evaluator ExportedFunc;

Just few type declarations, as you can see, we introduce a new delegate type matching the type of the Dll function. MarshalAs attributes are important to keep unicode information.


IntPtr formulaLib = LoadLibrary("myNice.dll");

if (formulaLib == IntPtr.Zero) throw new DllNotFoundException("myNice.dll");

IntPtr exportedFunc = GetProcAddress(formulaLib, "myExportedFunction");

if (exportedFunc == IntPtr.Zero) throw new MyNiceException("The dll doesn't export the good functions");

ExportedFunc = (Evaluator)Marshal.GetDelegateForFunctionPointer(exportedFunc, typeof(Evaluator));

And that’s it. Now, you can normally call ExportedFunc from .net without any problems. You must remember to do the same for the begin/end runtime, call them first from .net, and you’re done. The gates to Haskell paradise just pop’ed in your Visual Studio world.

Conclusion

So good luck combining the best of both worlds. I don’t know if the DLL problem with GHC (version 6.10.4) will be fixed soon or not, but in the mean time you can still use this workaround.

There is an interesting follow-up in the stackoverflow question explaining how to avoid all my problems in a much nicer way. To bad I didn’ find it myself…

Tags: , , ,

Leave a Reply