This is probably a really easy question to answer, but for some reason I'm really struggling with it.
I have a DLL written in C to access hardware at a protocol level, and I want to write a Haskell program that calls some of those C functions. Here's a snippet of the relevant C header (with names just slightly obfuscated due to possible copyrighting issues):
#ifdef HWDRIVER_EXPORTS
#define HWDRIVER_API __declspec(dllexport)
#else
#define HWDRIVER_API __declspec(dllimport)
#endif
HWDRIVER_API int HW_Init(void);
This has been compiled as a DLL in Visual Studio 2003, and I have successfully loaded the DLL from both C and C#, so I am confident that the DLL works fine. The DLL is named "hw-driver.dll".
Next, here's the Haskell source code just to test if I can properly load the DLL and call the simplest function in it:
{-# LANGUAGE ForeignFunctionInterface #-}
module Main
where
import Foreign
import Foreign.C
foreign import stdcall "hw-driver" "HW_Init" hwInit :: IO (CInt)
main = do
x <- hwInit
if x == 0
then putStr "Successfully initialized"
else putStr "Could not initialize"
The line that is giving me trouble is the foreign import line. As I understand it, the syntax is foreign (import/export) (ccall/stdcall) library-name C-function-name haskell-function-name :: Haskell type declaration. So mine should be foreign import stdcall (because you use stdcall when loading a DLL in Win32) "hw-driver" (because the file is named "hw-driver.dll" and it is located in the same directory as dlltest.hs) "HW_Init" (the name of the function in C) hwInit :: IO (Cint) (void arguments, returning an int).
However, when I try running ghci dlltest.hs
, I get the following output:
[1 of 1] Compiling Main ( dlltest.hs, interpreted )
dlltest.hs:8:43: parse error on input `"'
Failed, modules loaded: none.
Line 8, column 43 is the first quotation mark on HW_Init. Okay, so maybe I have to put both the library name and the function name in one string, I've seen that in a few places. If I try running that, then I get:
[1 of 1] Compiling Main ( dlltest.hs, interpreted )
dlltest.hs:8:23: Malformed entity string
Failed, modules loaded: none.
8:23 is the first quotation mark of the new string "hw-driver HW_Init".
I don't believe there's anything wrong with my ghc setup (6.10.3), because I can run the following code that was copy-pasted from Real World Haskell in ghci:
{-- snippet pragma --}
{-# LANGUAGE ForeignFunctionInterface #-}
{-- /snippet pragma --}
{-- snippet imports --}
import Foreign
import Foreign.C.Types
{-- /snippet imports --}
{-- snippet binding --}
foreign import ccall "math.h sin"
c_sin :: CDouble -> CDouble
{-- /snippet binding --}
{-- snippet highlevel --}
fastsin :: Double -> Double
fastsin x = realToFrac (c_sin (realToFrac x))
{-- /snippet highlevel --}
{-- snippet use --}
main = mapM_ (print . fastsin) [0/10, 1/10 .. 10/10]
{-- /snippet use --}
So long question short, how do I properly declare a foreign import on a Win32 DLL? I haven't been able to find anything on Google.
And to kind of tag along on that question, will I be able to use a program like c2hs or hsc2hs to parse the header file hw-driver.h
so I don't have to manually write the foreign import calls for all 20-25 functions contained in that DLL? I haven't been able to find any decent examples of that either.
EDIT: ephemient has pointed out that the correct syntax for the foreign import line is:
foreign import stdcall "hw-driver.h HW_Init" hwInit :: IO CInt
With this, I am able to call ghci dlltest.hs -lhw-driver
and properly call the main function with a successful return code. However, the command ghc --make dlltest.hs -lhw-driver
fails with a linker error. So, here's the verbose output of that command (note that I have all of hw-driver.{dll,h,lib} in the working directory):
Glasgow Haskell Compiler, Version 6.10.3, for Haskell 98, stage 2 booted by GHC version 6.10.1
Using package config file: C:\ghc\ghc-6.10.3\package.conf
hiding package base-3.0.3.1 to avoid conflict with later version base-4.1.0.0
wired-in package ghc-prim mapped to ghc-prim-0.1.0.0
wired-in package integer mapped to integer-0.1.0.1
wired-in package base mapped to base-4.1.0.0
wired-in package rts mapped to rts-1.0
wired-in package haskell98 mapped to haskell98-1.0.1.0
wired-in package syb mapped to syb-0.1.0.1
wired-in package template-haskell mapped to template-haskell-2.3.0.1
wired-in package dph-seq mapped to dph-seq-0.3
wired-in package dph-par mapped to dph-par-0.3
Hsc static flags: -static
*** Chasing dependencies:
Chasing modules from: *dlltest.hs
Stable obj: [Main]
Stable BCO: []
Ready for upsweep
[NONREC
ModSummary {
ms_hs_date = Mon Jun 22 13:20:05 Eastern Daylight Time 2009
ms_mod = main:Main,
ms_imps = [Foreign.C, Foreign]
ms_srcimps = []
}]
compile: input file dlltest.hs
Created temporary directory: C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0
*** Checking old interface for main:Main:
[1 of 1] Skipping Main ( dlltest.hs, dlltest.o )
*** Deleting temp files:
Deleting: C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.s
Warning: deleting non-existent C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.s
Upsweep completely successful.
*** Deleting temp files:
Deleting:
link: linkables are ...
LinkableM (Mon Jun 22 13:22:26 Eastern Daylight Time 2009) main:Main
[DotO dlltest.o]
Linking dlltest.exe ...
*** Windres:
C:\ghc\ghc-6.10.3\bin/windres --preprocessor="C:\ghc\ghc-6.10.3\gcc" "-BC:\ghc\ghc-6.10.3\gcc-lib/" "-IC:\ghc\ghc-6.10.3\include/mingw" "-E" "-xc" "-DRC_INVOKED" --use-temp-file --input=C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.rc --output=C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.o --output-format=coff
*** Linker:
C:\ghc\ghc-6.10.3\gcc -BC:\ghc\ghc-6.10.3\gcc-lib/ -IC:\ghc\ghc-6.10.3\include/mingw -v -o dlltest.exe -DDONT_WANT_WIN32_DLL_SUPPORT dlltest.o -lhw-driver C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.o -LC:\ghc\ghc-6.10.3\base-4.1.0.0 -LC:\ghc\ghc-6.10.3\integer-0.1.0.1 -LC:\ghc\ghc-6.10.3\ghc-prim-0.1.0.0 -LC:\ghc\ghc-6.10.3 -LC:\ghc\ghc-6.10.3/gcc-lib -lHSbase-4.1.0.0 -lwsock32 -lmsvcrt -lkernel32 -luser32 -lshell32 -lHSinteger-0.1.0.1 -lHSghc-prim-0.1.0.0 -lHSrts -lm -lffi -lgmp -lwsock32 -u _ghczmprim_GHCziTypes_Izh_static_info -u _ghczmprim_GHCziTypes_Czh_static_info -u _ghczmprim_GHCziTypes_Fzh_static_info -u _ghczmprim_GHCziTypes_Dzh_static_info -u _base_GHCziPtr_Ptr_static_info -u _base_GHCziWord_Wzh_static_info -u _base_GHCziInt_I8zh_static_info -u _base_GHCziInt_I16zh_static_info -u _base_GHCziInt_I32zh_static_info -u _base_GHCziInt_I64zh_static_info -u _base_GHCziWord_W8zh_static_info -u _base_GHCziWord_W16zh_static_info -u _base_GHCziWord_W32zh_static_info -u _base_GHCziWord_W64zh_static_info -u _base_GHCziStable_StablePtr_static_info -u _ghczmprim_GHCziTypes_Izh_con_info -u _ghczmprim_GHCziTypes_Czh_con_info -u _ghczmprim_GHCziTypes_Fzh_con_info -u _ghczmprim_GHCziTypes_Dzh_con_info -u _base_GHCziPtr_Ptr_con_info -u _base_GHCziPtr_FunPtr_con_info -u _base_GHCziStable_StablePtr_con_info -u _ghczmprim_GHCziBool_False_closure -u _ghczmprim_GHCziBool_True_closure -u _base_GHCziPack_unpackCString_closure -u _base_GHCziIOBase_stackOverflow_closure -u _base_GHCziIOBase_heapOverflow_closure -u _base_ControlziExceptionziBase_nonTermination_closure -u _base_GHCziIOBase_blockedOnDeadMVar_closure -u _base_GHCziIOBase_blockedIndefinitely_closure -u _base_ControlziExceptionziBase_nestedAtomically_closure -u _base_GHCziWeak_runFinalizzerBatch_closure -u _base_GHCziTopHandler_runIO_closure -u _base_GHCziTopHandler_runNonIO_closure -u _base_GHCziConc_runHandlers_closure -u _base_GHCziConc_ensureIOManagerIsRunning_closure
Reading specs from C:/ghc/ghc-6.10.3/gcc-lib/specs
Configured with: ../gcc-3.4.5-20060117-3/configure --with-gcc --with-gnu-ld --with-gnu-as --host=mingw32 --target=mingw32 --prefix=/mingw --enable-threads --disable-nls --enable-languages=c,c++,f77,ada,objc,java --disable-win32-registry --disable-shared --enable-sjlj-exceptions --enable-libgcj --disable-java-awt --without-x --enable-java-gc=boehm --disable-libgcj-debug --enable-interpreter --enable-hash-synchronization --enable-libstdcxx-debug
Thread model: win32
gcc version 3.4.5 (mingw-vista special r3)
C:/ghc/ghc-6.10.3/gcc-lib/collect2.exe -Bdynamic -o dlltest.exe -u _ghczmprim_GHCziTypes_Izh_static_info -u _ghczmprim_GHCziTypes_Czh_static_info -u _ghczmprim_GHCziTypes_Fzh_static_info -u _ghczmprim_GHCziTypes_Dzh_static_info -u _base_GHCziPtr_Ptr_static_info -u _base_GHCziWord_Wzh_static_info -u _base_GHCziInt_I8zh_static_info -u _base_GHCziInt_I16zh_static_info -u _base_GHCziInt_I32zh_static_info -u _base_GHCziInt_I64zh_static_info -u _base_GHCziWord_W8zh_static_info -u _base_GHCziWord_W16zh_static_info -u _base_GHCziWord_W32zh_static_info -u _base_GHCziWord_W64zh_static_info -u _base_GHCziStable_StablePtr_static_info -u _ghczmprim_GHCziTypes_Izh_con_info -u _ghczmprim_GHCziTypes_Czh_con_info -u _ghczmprim_GHCziTypes_Fzh_con_info -u _ghczmprim_GHCziTypes_Dzh_con_info -u _base_GHCziPtr_Ptr_con_info -u _base_GHCziPtr_FunPtr_con_info -u _base_GHCziStable_StablePtr_con_info -u _ghczmprim_GHCziBool_False_closure -u _ghczmprim_GHCziBool_True_closure -u _base_GHCziPack_unpackCString_closure -u _base_GHCziIOBase_stackOverflow_closure -u _base_GHCziIOBase_heapOverflow_closure -u _base_ControlziExceptionziBase_nonTermination_closure -u _base_GHCziIOBase_blockedOnDeadMVar_closure -u _base_GHCziIOBase_blockedIndefinitely_closure -u _base_ControlziExceptionziBase_nestedAtomically_closure -u _base_GHCziWeak_runFinalizzerBatch_closure -u _base_GHCziTopHandler_runIO_closure -u _base_GHCziTopHandler_runNonIO_closure -u _base_GHCziConc_runHandlers_closure -u _base_GHCziConc_ensureIOManagerIsRunning_closure C:/ghc/ghc-6.10.3/gcc-lib/crt2.o C:/ghc/ghc-6.10.3/gcc-lib/crtbegin.o -LC:\ghc\ghc-6.10.3\base-4.1.0.0 -LC:\ghc\ghc-6.10.3\integer-0.1.0.1 -LC:\ghc\ghc-6.10.3\ghc-prim-0.1.0.0 -LC:\ghc\ghc-6.10.3 -LC:\ghc\ghc-6.10.3/gcc-lib -LC:/ghc/ghc-6.10.3/gcc-lib dlltest.o -lhw-driver C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.o -lHSbase-4.1.0.0 -lwsock32 -lmsvcrt -lkernel32 -luser32 -lshell32 -lHSinteger-0.1.0.1 -lHSghc-prim-0.1.0.0 -lHSrts -lm -lffi -lgmp -lwsock32 -lmingw32 -lgcc -lmoldname -lmingwex -lmsvcrt -luser32 -lkernel32 -ladvapi32 -lshell32 -lmingw32 -lgcc -lmoldname -lmingwex -lmsvcrt C:/ghc/ghc-6.10.3/gcc-lib/crtend.o
C:\ghc\ghc-6.10.3\gcc-lib\ld.exe: cannot find -lhw-driver
collect2: ld returned 1 exit status
*** Deleting temp files:
Deleting: C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.o C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0/ghc4428_0.rc
*** Deleting temp dirs:
Deleting: C:\DOCUME~1\CHRISC~1\LOCALS~1\Temp\/ghc4428_0
foreign import
stdcall
which I believed to be correct with a DLL built in Visual Studio 2003. I had to download the pexports
tool for MinGW, which lists the functions exported from a DLL. The linker had been looking for HWInit@0 the whole time, but pexports
said the DLL was exporting just HWInit.
I changed my line to foreign import
ccall
instead, and I was successfully able to link the program using either of ghc --make dlltest.hs hw-driver.lib
or ghc --make dlltest.hs -L. -lhw-driver
due to having both the .lib and the .dll file available in the working directory.
FFI spec # 4.1.1 Import Declarations,
impent → " [
static
] [chname] [&] [cid] "
| "dynamic
"
| "wrapper
"
where chname is "C header name", not "library name".
FFI spec # 4.1.4 Specification of Header Files
A C header specified in an import declaration is always included by
#include "
chname"
. There is no explicit support for#include <
chname>
style inclusion. The ISO C99 [3] standard guarantees that any search path that would be used for a#include <
chname>
is also used for#include "
chname"
and it is guaranteed that these paths are searched after all paths that are unique to#include "
chname"
. Furthermore, we require that chname ends on.h
to make parsing of the specification of external entities unambiguous.
Try with a proper header name,
foreign import stdcall "hw-driver.h HW_Init" hwInit :: IO CInt
or with no header name at all.
foreign import stdcall "HW_Init" hwInit :: IO CInt
Your command line doesn't seem to include .
as a library search path. It is most likely that this is the problem. GHCi magically includes .
in the library search path.
ghc --make dlltest.hs -L. -lhwdriver
If that still fails, maybe it's the static library which is causing issues. Unlikely, but...
GHC on Windows uses dynamic linking by default. Since you have a .lib
, which is a static library, try informing the linker that you want static linking.
ghc --make dlltest.hs -L. -optl-Bstatic -lhwdriver -optl-Bdynamic
As for auto-generated bindings, there's
I've found c2hs to be the easiest to use, but I've never tried it on anything requiring stdcall
s.
It's not that onerous to write all the foreign
stuff manually, if there's only 25 calls or so. I managed to manually write bindings to libvlc
a few years back, for some little project...
Below is a working example that calls [GetComputerName](http://msdn.microsoft.com/en-us/library/ms724295(VS.85).aspx) from kernel32.dll
:
{-# LANGUAGE ForeignFunctionInterface #-}
module Main where
import Control.Monad
import Foreign.C
import Foreign.Marshal.Alloc
import Foreign.Marshal.Array
import System.Win32.Types
foreign import stdcall "GetComputerNameW"
win32_getComputerName :: LPTSTR -> LPDWORD -> IO Bool
getComputerName :: IO String
getComputerName = do
withTString maxBuf $
\buf -> do
alloca $ \len -> do
pokeArray len [fromIntegral maxLength]
success <- win32_getComputerName buf len
when (not success) $ fail "GetComputerName failed"
[len'] <- peekArray 1 len
peekTStringLen (buf, (fromIntegral len'))
where
maxBuf = take maxLength $ repeat 'x'
maxLength = 15 -- cheating
main :: IO ()
main = getComputerName >>= putStrLn
Build it with
ghc --make compname.hs -lkernel32
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With