Win32 dlls -- how to do it?

Kevin S. Millikin kmillikin@atcorp.com" <kmillikin@atcorp.com
Fri, 27 Jun 2003 14:35:15 -0500


    ac> New to Haskell, new to GHC.  My initial intention in picking up
    ac> Haskell is to be able to write some functions in Haskell and 
then
    ac> compile them into .dlls for Win32 that I can call from programs
    ac> written in other languages.  So before I get too deeply 
invested I
    ac> want to make sure that is possible.

It's certainly possible.  I do it here all the time on a commercial
project.  I even volunteered a few months ago to write some better
documentation, but I sadly have not had the time to do it.

As Sigbjorn says, adding -package base when linking the DLL with ghc 
will
silence those errors from the linker.

The warning given when compiling the C code in dllMain.c comes from the
declaration of __stginit_Adder as

EXTFUN(__stginit_Adder)

Which is not really the type that startupHaskell expects.  Declare it
instead as

extern void __stginit_Adder(void);

and the warning will be silenced.

The much bigger gotcha is that the code in dllMain.c from Sect 11 of 
the
user's guide will probably not work, because Windows forbids certain 
things
from happening in dllMain (basically, anything that could possibly 
cause one
to reattach to the DLL).  That includes creating new threads, and that
includes creating timers, and startupHaskell tries to do that.  I don't
remember at the moment if it is a problem when the GHC runtime is in a
separate DLL, but it is certainly a problem when linking the GHC 
runtime
into the same DLL.

My typical solution is to provide entry points to explicitly startup 
and
shutdown the Haskell runtime, outside of a call to dllMain.  Here is an
example, calling Adder from Java:

1. Write some Haskell code to implement the add function in the DLL. 
 Note
   that Java will expect a mangled name, which we supply manually:

==== Adder.hs
module Adder (add) where

import Foreign (Ptr, Int32)

data JNIEnv
data JClass

foreign export stdcall "Java_Adder_add"
    add :: Ptr JNIEnv -> Ptr JClass -> Int32 -> Int32 -> IO Int32

add :: Ptr JNIEnv -> Ptr JClass -> Int32 -> Int32 -> IO Int32
add _ _ m n = return (m + n)
====================

2. Compile.  Don't forget -fglasgow-exts:

  ghc -fglasgow-exts -c Adder.hs

3. Write C functions that can be called by Java (mangling the names 
again)
   and that can be used to startup and shutdown the Haskell runtime. 
 You
   can't do this directly from Java, because the FFI functions don't 
have
   the mangled names that Java expects, and you can't do it from 
Haskell
   code for obvious reasons.

==== ccode.c
#include <jni.h>
#include <HsFFI.h>

extern void __stginit_Adder(void);

static char *args[] = { "ghcDll", 0 };
static int argc = 1;
static char **argv = args;

JNIEXPORT void JNICALL Java_Adder_haskellInit(JNIEnv *e, jclass c) {
    hs_init(&argc, &argv);
    hs_add_root(__stginit_Adder);
}

JNIEXPORT void JNICALL Java_Adder_haskellExit(JNIEnv *e, jclass c) {
    hs_exit();
}
====================

4. Compile it.  JAVAHOME is presumed to be set to the root of your Java
   install (the directory containing the "include" subdirectory):

  ghc -I$JAVAHOME/include -I$JAVAHOME/include/win32 -optc -mno-cygwin 
-c ccode.c

5. Link into DLL, using ghc (I generally call dllwrap directly, because 
it
   gives me finer (actually, just less verbose) control over DLL 
options):

  ghc --mk-dll -optdll --add-stdcall-alias -o adder.dll Adder.o 
Adder_stub.o ccode.o -package base

   "-optdll --add-stdcall-alias" is important, because Java can't find 
the
   entries otherwise.

6. Write a Java driver that loads the native library (the DLL), 
initializes
   the Haskell runtime, calls our simple DLL function, and then 
shutdowns
   the Haskell runtime:

==== Adder.java
public class Adder {
  static {
    System.loadLibrary("adder");
    haskellInit(); }

  public static native void haskellInit();
  public static native void haskellExit();

  public static native int add(int m, int n);

  public static void main(String[] args) {
    try {
      System.out.println("Answer is: " + Adder.add(32, 10)); }
    finally {
      haskellExit(); }}}
====================

7. Compile the Java code:

  javac -classpath . Adder.java

8. Enjoy:

  java -classpath . Adder

----
Kevin S. Millikin           Architecture Technology Corporation
Research Scientist          Specialists in Computer Architecture
(952)829-5864 x162          http://www.atcorp.com