Monday, November 23, 2009

Haskell DLL's on Windows

The current section of the GHC manual on creating DLL's on Windows is fairly confusing to read, and has some bugs (i.e. 3605). Since I got tripped up by the current documentation, I offered to rewrite sections 11.6.2 and 11.6.3 (merging them in the process). Creating Windows DLL's with GHC is surprisingly easy, and my revised manual section includes an example which can be called from both Microsoft Word (using VBA) and C++. I've pasted the revised manual section as the rest of this blog post. I'll shortly be submitting it to the GHC team, so any feedback is welcome.

11.6.2. Making DLLs to be called from other languages

This section describes how to create DLLs to be called from other languages, such as Visual Basic or C++. This is a special case of Section, "Making a Haskell library that can be called from foreign code"; we'll deal with the DLL-specific issues that arise below. Here's an example:

Use foreign export declarations to export the Haskell functions you want to call from the outside. For example:

-- Adder.hs
{-# LANGUAGE ForeignFunctionInterface #-}
module Adder where

adder :: Int -> Int -> IO Int -- gratuitous use of IO
adder x y = return (x+y)

foreign export stdcall adder :: Int -> Int -> IO Int

Add some helper code that starts up and shuts down the Haskell RTS:

// StartEnd.c
#include <Rts.h>

extern void __stginit_Adder(void);

void HsStart()
int argc = 1;
char* argv[] = {"ghcDll", NULL}; // argv must end with NULL

// Initialize Haskell runtime
char** args = argv;
hs_init(&argc, &args);

// Tell Haskell about all root modules

void HsEnd()

Here, Adder is the name of the root module in the module tree (as mentioned above, there must be a single root module, and hence a single module tree in the DLL). Compile everything up:

$ ghc -c Adder.hs
$ ghc -c StartEnd.c
$ ghc -shared -o Adder.dll Adder.o Adder_stub.o StartEnd.o

Now the file Adder.dll can be used from other programming languages. Before calling any functions in Adder it is necessary to call HsStart, and at the very end call HsEnd.

NOTE: It may appear tempting to use DllMain to call hs_init/hs_exit, but this won’t work (particularly if you compile with -threaded). Using from VBA

An example of using Adder.dll from VBA is:

Private Declare Function Adder Lib "Adder.dll" Alias "adder@8" _
(ByVal x As Long, ByVal y As Long) As Long

Private Declare Sub HsStart Lib "Adder.dll" ()
Private Declare Sub HsEnd Lib "Adder.dll" ()

Private Sub Document_Close()
End Sub

Private Sub Document_Open()
End Sub

Public Sub Test()
MsgBox "12 + 5 = " & Adder(12, 5)
End Sub

This example uses the Document_Open/Close functions of Microsoft Word, but provided HsStart is called before the first function, and HsEnd after the last, then it will work fine. Using from C++

An example of using Adder.dll from C++ is:

// Tester.cpp
#include "HsFFI.h"
#include "Adder_stub.h"
#include <stdio.h>

extern "C" {
void HsStart();
void HsEnd();

int main()
// can now safely call functions from the DLL
printf("12 + 5 = %i\n", adder(12,5)) ;
return 0;

This can be compiled and run with:

$ ghc -o tester Tester.cpp Adder.dll.a
$ tester
12 + 5 = 17

Please give feedback in the comments.


Anonymous said...

I'd suggest linking to the GHC bug you mention (3605) from the "don't call hs_init/hs_exit from DllMain" note - it's very frustrating to be told that something you may well want to do won't work without any details.

Neil Mitchell said...

Ganesh: Very good point, especially since if people try the DllMain thing it's very likely to work for them.

Felix said...

A related serious issue is that there does not seem to be a plan for a 64-bit Windows port. More and more Windows users switch to 64-bit. Windows Azure is based on 64-bit Windows Server. While i like Haskell in general more than F#, Haskell on Windows is approaching a dead end.

Neil Mitchell said...

Felix: Haskell on Windows is far from at a dead end. So far, very few people use Windows 64 bit. By the time it's anywhere close to mainstream it will be ported over - it's probably not that hard to do. There really is nothing to worry about.

aquarin said...

Thanks for the tutorial. I am getting some linking errors on the last step (under Ubuntu):

Tester.cpp: In function ‘int main()’:

warning: format ‘%i’ expects type ‘int’, but argument 2 has type ‘HsInt’
/usr/bin/ld: dynamic variable `base_GHCziIOBase_lvl22_closure' is zero size

Neil Mitchell said...

Watto: I only intended the example to work on Windows, but if it could be easily made to work on all platforms that would be great. I know GHC 6.10 doesn't really support DLL's on Linux, and I think the GHC 6.12 support is still in progress. I suggest you ask on the glasgow-haskell-users -AT- mailing list.

crafter said...

Your post is really helpful. But I experience problem when I try to link Haskell-DLL which uses hackage library (XmlRpc). As I understand, GHC requires me to specify ALL packages I use explicitly or implictly. It is terribly inconvinient. I'd appreciate very much if you gave any workaround. Thank you!

Neil Mitchell said...

crafter: GHC does require you to specify all the packages manually, unless you specify --make. You may find that there is somewhere you can put --make in that list of actions to get it working.

The intention is that Cabal will help you make DLL's and handle all this stuff automatically, but it's not ready yet.

HeavensRevenge said...

I must agree with Felix, x64 on Windows to me is a VERY massive issue, yes to the point of making me try and do a partial port.
If I may suggest, even though Haskell is general purpose to span the spectrum of redneck to doctoral users, I would gamble to say that most users are quite intelligent and current in todays leading technological advancements, not all the 1970's old school VAX user who is afraid of change.
The advantages of using 2x larger lambda's would of course be excellent news, x64 code regardless of word size or lambda expression size, does indeed run ~2x faster on a very excellent implementation, since I have measured a 192% increase primarily.
If windoze users using Haskell for the first time see it's speed and elegance their first go, we would most likely have more users with a Win64 native port seeing how windows is still an OS with a very large user base.

Unknown said...

Neil: the current docs have a section entitled "Beware of DllMain!" which describes the problems with calling hs_init() from DllMain in detail. I suggest keeping that section, perhaps moving it to the end with a forward ref from "NOTE: don't call hs_init/hs_exit from DllMain".

HeavensRevenge: a 2x speedup from 64-bit code is highly unlikely. The extra memory pressure tends to slow things down, so there's a tradeoff against the larger register set. We see roughly the same performance between 32 and 64 bit on Linux. The advantage of 64-bit is really only for applications that need more than 2Gb of memory. Also I'm not sure what you mean by "lambda expression size".

Neil Mitchell said...

Simon: It has a section, but I'm not convinced it's correct. It seems to imply that there are some situations when putting the code in DllMain works, but I think that's by coincidence and isn't guaranteed, and may break in future GHC versions.

I intend to Docbook this as soon as I get GHC building on my work machine, but it's been very busy with other stuff lately.

Unknown said...

My main concern is that I don't want to lose the text that explains *why* using DllMain() is wrong, otherwise we're doomed to rediscover it in the future. By all means trim/fix/rewrite or whatever to make it correct.