Haskell import import stdcall sulla funzione DLL
Domanda
Questa è probabilmente una domanda molto semplice a cui rispondere, ma per qualche ragione sto davvero lottando con essa.
Ho una DLL scritta in C per accedere all'hardware a livello di protocollo e voglio scrivere un programma Haskell che chiama alcune di queste funzioni C. Ecco un frammento dell'intestazione C pertinente (con nomi leggermente offuscati a causa di possibili problemi di copyright):
#ifdef HWDRIVER_EXPORTS
#define HWDRIVER_API __declspec(dllexport)
#else
#define HWDRIVER_API __declspec(dllimport)
#endif
HWDRIVER_API int HW_Init(void);
Questo è stato compilato come DLL in Visual Studio 2003 e ho caricato correttamente la DLL da C e C #, quindi sono sicuro che la DLL funzioni correttamente. La DLL è denominata & Quot; hw-driver.dll & Quot ;.
Quindi, ecco il codice sorgente di Haskell solo per testare se posso caricare correttamente la DLL e chiamare la funzione più semplice in essa:
{-# 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"
La linea che mi dà problemi è la linea di importazione estera. A quanto ho capito, la sintassi è straniera (import / export) (ccall / stdcall) nome-libreria nome-funzione-C nome-funzione-haskell :: dichiarazione del tipo Haskell . Quindi il mio dovrebbe essere import import stdcall (perché usi stdcall quando carichi una DLL in Win32) & Quot; hw-driver & Quot; (perché il file è denominato " hw-driver.dll " e si trova nella stessa directory di dlltest.hs) " HW_Init " (il nome della funzione in C) hwInit :: IO (Cint) (argomenti vuoti, restituendo un int).
Tuttavia, quando provo a eseguire ghci dlltest.hs
, ottengo il seguente output:
[1 of 1] Compiling Main ( dlltest.hs, interpreted )
dlltest.hs:8:43: parse error on input `"'
Failed, modules loaded: none.
Riga 8, colonna 43 è la prima virgoletta su HW_Init. Ok, quindi forse devo mettere sia il nome della libreria che il nome della funzione in una stringa, l'ho visto in alcuni punti. Se provo a eseguirlo, ottengo:
[1 of 1] Compiling Main ( dlltest.hs, interpreted )
dlltest.hs:8:23: Malformed entity string
Failed, modules loaded: none.
8:23 è il primo segno di virgolette della nuova stringa " hw-driver HW_Init " ;.
Non credo che ci sia qualcosa di sbagliato nella mia configurazione di ghc (6.10.3), perché posso eseguire il seguente codice che è stato incollato da 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 --}
In breve, breve domanda, come posso dichiarare correttamente un'importazione straniera su una DLL Win32? Non sono stato in grado di trovare nulla su Google.
E per tipo di tag lungo quella domanda, sarò in grado di usare un programma come c2hs o hsc2hs per analizzare il file di intestazione hw-driver.h
in modo da non dover scrivere manualmente le chiamate di importazione esterne per tutti i 20- 25 funzioni contenute in quella DLL? Nemmeno io sono stato in grado di trovare esempi decenti.
EDIT: ephemient ha sottolineato che la sintassi corretta per la linea di importazione estera è:
foreign import stdcall "hw-driver.h HW_Init" hwInit :: IO CInt
Con questo, sono in grado di chiamare ghci dlltest.hs -lhw-driver
e chiamare correttamente la funzione principale con un codice di ritorno riuscito. Tuttavia, il comando ghc --make dlltest.hs -lhw-driver
ha esito negativo con un errore del linker. Quindi, ecco l'output dettagliato di quel comando (nota che ho tutto il driver hw. {Dll, h, lib} nella directory di lavoro):
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
A quanto pare, il collegamento reale non è stato così difficile come lo stavo facendo. Stavo usando
foreign import
stdcall
che ritenevo corretto con una DLL costruita in Visual Studio 2003. Ho dovuto scaricare lo strumento pexports
per MinGW, che elenca le funzioni esportate da un DLL. Il linker era sempre alla ricerca di HWInit @ 0, ma ccall
ha affermato che la DLL esportava solo HWInit.
Ho invece modificato la mia riga in ghc --make dlltest.hs hw-driver.lib
ghc --make dlltest.hs -L. -lhw-driver
e sono stato in grado di collegare correttamente il programma utilizzando <=> o <=> grazie alla presenza sia di .lib che di il file .dll disponibile nella directory di lavoro.
Soluzione
Specifiche FFI # 4.1.1 Dichiarazioni di importazione ,
impent & # 8594; & Quot; [
static
] [ chname ] [& amp;] [ cid ] "
nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; | & Quot;dynamic
"
nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; nbsp &; | & Quot;wrapper
"
dove chname è " Nome intestazione C " ;, non " nome libreria " ;.
Specifiche FFI # 4.1.4 Specifica dei file di intestazione
Un'intestazione C specificata in una dichiarazione di importazione è sempre inclusa da
#include "
chname"
. Non esiste un supporto esplicito per l'inclusione dello stile#include <
chname>
. ISO C99 [ 3 ] standard garantisce che qualsiasi percorso di ricerca che verrebbe utilizzato per un.h
chname.
viene utilizzato anche per.lib
chnamestdcall
ed è garantito che questi percorsi vengono cercati dopo tutti i percorsi che sono unici perforeign
chnamelibvlc
. Inoltre, richiediamo che chname termina con <=> per rendere inequivocabile l'analisi delle specifiche di entità esterne.
Prova con un nome di intestazione corretto,
foreign import stdcall "hw-driver.h HW_Init" hwInit :: IO CInt
o senza alcun nome di intestazione.
foreign import stdcall "HW_Init" hwInit :: IO CInt
La tua riga di comando non sembra includere <=> come percorso di ricerca nella libreria. È molto probabile che questo sia il problema. GHCi include magicamente <=> nel percorso di ricerca della libreria.
ghc --make dlltest.hs -L. -lhwdriver
Se il problema persiste, forse è la libreria statica a causare problemi. Improbabile, ma ...
GHC su Windows utilizza il collegamento dinamico per impostazione predefinita. Dato che hai una <=>, che è una libreria statica, prova a informare il linker che desideri un collegamento statico.
ghc --make dlltest.hs -L. -optl-Bstatic -lhwdriver -optl-Bdynamic
Per quanto riguarda i binding generati automaticamente, c'è
Ho trovato c2hs il più semplice da usare, ma non l'ho mai provato su nulla che richiedesse <=> s.
Non è così oneroso scrivere tutte le <=> cose manualmente, se ci sono solo 25 chiamate. Sono riuscito a scrivere manualmente i binding su <=> qualche anno fa, per qualche piccolo progetto ...
Altri suggerimenti
Di seguito è riportato un esempio funzionante che chiama [GetComputerName] ( http://msdn.microsoft.com/en-us/library/ms724295 (VS.85) .aspx) da 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
Costruiscilo con
ghc --make compname.hs -lkernel32