Haskell importation stdcall étrangère sur la fonction DLL
Question
C’est probablement une question très facile à répondre, mais pour une raison quelconque, j’ai vraiment du mal à la résoudre.
J'ai une DLL écrite en C pour accéder au matériel à un niveau de protocole et je veux écrire un programme Haskell qui appelle certaines de ces fonctions C. Voici un extrait de l'en-tête C pertinent (avec des noms légèrement obscurcis en raison de problèmes de copyright):
#ifdef HWDRIVER_EXPORTS
#define HWDRIVER_API __declspec(dllexport)
#else
#define HWDRIVER_API __declspec(dllimport)
#endif
HWDRIVER_API int HW_Init(void);
Ceci a été compilé en tant que DLL dans Visual Studio 2003 et j’ai chargé avec succès la DLL à partir de C et de C #. Je suis donc convaincu que la DLL fonctionne correctement. La DLL est nommée & Quot; hw-driver.dll & Quot;.
.Ensuite, voici le code source Haskell juste pour vérifier si je peux charger correctement la DLL et appeler la fonction la plus simple:
{-# 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 ligne qui me pose problème est la ligne d'importation étrangère. Si je comprends bien, la syntaxe est étrangère (import / export) (ccall / stdcall) nom-bibliothèque nom-fonction-C nom-fonction-haskell :: Déclaration de type Haskell . Donc, le mien doit être importé de l'étranger stdcall (car vous utilisez stdcall lors du chargement d'une DLL dans Win32) & "Hw-driver &"; (car le fichier s'appelle & "; hw-driver.dll &"; il se trouve dans le même répertoire que dlltest.hs) & "HW_Init &"; (le nom de la fonction en C) hwInit :: IO (Cint) (arguments void, retournant un int).
Cependant, lorsque j'essaie d'exécuter ghci dlltest.hs
, le résultat suivant est généré:
[1 of 1] Compiling Main ( dlltest.hs, interpreted )
dlltest.hs:8:43: parse error on input `"'
Failed, modules loaded: none.
La ligne 8, colonne 43 est le premier guillemet sur HW_Init. Ok, alors peut-être que je dois mettre le nom de la bibliothèque et le nom de la fonction dans une chaîne, je l'ai vu à quelques endroits. Si j'essaie de l'exécuter, alors je reçois:
[1 of 1] Compiling Main ( dlltest.hs, interpreted )
dlltest.hs:8:23: Malformed entity string
Failed, modules loaded: none.
8:23 est le premier guillemet de la nouvelle chaîne " hw-driver HW_Init "..
Je ne crois pas qu'il y ait quelque chose qui cloche dans ma configuration ghc (6.10.3), car je peux exécuter le code suivant copié-collé depuis Real World Haskell dans 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 --}
Très longue question, comment puis-je déclarer correctement une importation étrangère sur une DLL Win32? Je n'ai pas trouvé quoi que ce soit sur Google.
Et puis, pour répondre à cette question, pourrai-je utiliser un programme comme c2hs ou hsc2hs pour analyser le fichier d'en-tête hw-driver.h
, de sorte que je n'ai pas à écrire manuellement les appels d'importation étrangers pour tous les 20- 25 fonctions contenues dans cette DLL? Je n'ai pas été capable de trouver des exemples décents de cela non plus.
EDIT: ephemient a indiqué que la syntaxe correcte pour la ligne d'importation étrangère est la suivante:
foreign import stdcall "hw-driver.h HW_Init" hwInit :: IO CInt
Grâce à cela, je peux appeler ghci dlltest.hs -lhw-driver
et appeler correctement la fonction principale avec un code de retour réussi. Cependant, la commande ghc --make dlltest.hs -lhw-driver
échoue avec une erreur de l'éditeur de liens. Voici donc le résultat détaillé de cette commande (notez que j'ai tout le pilote hw-driver. {Dll, h, lib} dans le répertoire de travail):
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
Il s'est avéré que la liaison réelle n'était pas aussi difficile que je le pensais. J'utilisais
foreign import
stdcall
, ce qui, à mon avis, était correct avec une DLL créée dans Visual Studio 2003. Je devais télécharger l'outil pexports
pour MinGW, qui répertorie les fonctions exportées à partir d'un fichier. DLL. L'éditeur de liens avait toujours recherché HWInit @ 0, mais ccall
a déclaré que la DLL exportait uniquement HWInit.
J'ai remplacé ma ligne par ghc --make dlltest.hs hw-driver.lib
ghc --make dlltest.hs -L. -lhw-driver
et j'ai réussi à lier le programme à l'aide de <=> ou <=>, car les deux fichiers .lib et le fichier .dll disponible dans le répertoire de travail.
La solution
Spécification FFI 4.1.1 Déclarations d'importation ,
impent & # 8594; " [
static
] [ chname ] [& amp;] [ cid ] "
& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; | "dynamic
"
& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; | "wrapper
"
où chname est & "; nom d'en-tête C &", pas & "nom de bibliothèque &";
.Spécification FFI 4.1.4 Spécification des fichiers d’en-tête
Un en-tête C spécifié dans une déclaration d'importation est toujours inclus par
#include "
chname"
. Il n'y a pas de support explicite pour l'inclusion de style#include <
chname>
. La norme ISO C99 [ 3 ] standard garantit que tout chemin de recherche qui serait utilisé pour un.h
chname.
est également utilisé pour.lib
chnamestdcall
et il est garanti que Ces chemins sont recherchés après tous les chemins uniques àforeign
chnamelibvlc
. De plus, nous avons besoin que chname se termine le <=> pour rendre l'analyse de la spécification des entités externes non ambiguë.
Essayez avec un nom d'en-tête approprié,
foreign import stdcall "hw-driver.h HW_Init" hwInit :: IO CInt
ou sans nom d'en-tête.
foreign import stdcall "HW_Init" hwInit :: IO CInt
Votre ligne de commande ne semble pas inclure <=> comme chemin de recherche dans la bibliothèque. Il est fort probable que ce soit le problème. GHCi inclut par magie <=> dans le chemin de recherche de la bibliothèque.
ghc --make dlltest.hs -L. -lhwdriver
Si cela échoue toujours, c'est peut-être la bibliothèque statique qui pose problème. Peu probable, mais ...
GHC sous Windows utilise la liaison dynamique par défaut. Puisque vous avez une <=> bibliothèque statique, essayez d’informer l’éditeur de liens que vous souhaitez créer un lien statique.
ghc --make dlltest.hs -L. -optl-Bstatic -lhwdriver -optl-Bdynamic
En ce qui concerne les liaisons générées automatiquement, il y a
J'ai trouvé que c2hs était le plus facile à utiliser, mais je ne l'ai jamais essayé avec quoi que ce soit nécessitant <=> s.
Ce n'est pas que d'écrire tous les <=> fichiers manuellement, s'il n'y a que 25 appels ou plus. J'ai réussi à écrire manuellement des liaisons vers <=> quelques années en arrière, pour quelques petits projets ...
Autres conseils
Ci-dessous, un exemple de travail qui appelle [GetComputerName] ( http://msdn.microsoft.com/en-us/library/ms724295 (VS.85) .aspx) à partir de 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
Construisez-le avec
ghc --make compname.hs -lkernel32