2009-06-22 16 views
11

Ceci est probablement une question vraiment facile à répondre, mais pour une raison quelconque, je suis vraiment aux prises avec cela.Haskell import étranger stdcall sur la fonction DLL

J'ai une DLL écrite en C pour accéder au matériel au niveau du protocole, et je veux écrire un programme Haskell qui appelle certaines de ces fonctions C. Voici un extrait de l'en-tête correspondant C (avec des noms légèrement brouillées en raison de problèmes possibles copyrighting):

#ifdef HWDRIVER_EXPORTS 
#define HWDRIVER_API __declspec(dllexport) 
#else 
#define HWDRIVER_API __declspec(dllimport) 
#endif 
HWDRIVER_API int HW_Init(void); 

Cela a été compilé en tant que DLL dans Visual Studio 2003, et je l'ai chargé avec succès la DLL à la fois C et C#, donc je suis convaincu que la DLL fonctionne bien. La DLL est nommée "hw-driver.dll".

Ensuite, voici le code source Haskell juste pour tester si je peux charger correctement la DLL et appeler la fonction simple en elle:

{-# 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 donne du mal est la ligne d'importation étrangère. Si je comprends bien, la syntaxe est étrangère (import/export) (ccall/stdcall) bibliothèque nomC-nom_de_la_fonctionhaskell-nom_de_la_fonction :: déclaration de type Haskell. Donc le mien devrait être stdcall import étranger (parce que vous utilisez stdcall lors du chargement d'une DLL dans Win32) "hw-driver" (parce que le fichier est nommé "hw-driver.dll" et 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 vides, retournant un int).

Cependant, lorsque je tente en cours d'exécution ghci dlltest.hs, je reçois la sortie suivante:

[1 of 1] Compiling Main    (dlltest.hs, interpreted) 

dlltest.hs:8:43: parse error on input `"' 
Failed, modules loaded: none. 

ligne 8, colonne 43 est la première marque de cotation sur HW_Init. Bon, alors peut-être que je dois mettre à la fois le nom de la bibliothèque et le nom de la fonction dans une chaîne, j'ai vu ça dans quelques endroits. Si j'essaie en cours d'exécution, 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 la première marque de cotation de la nouvelle chaîne « HW_Init hw-pilote ».

Je ne crois pas qu'il y ait quelque chose de mal avec ma configuration GHC (6.10.3), parce que je peux exécuter le code suivant qui était la copie collée depuis Real World Haskell en 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 --} 

Tant question courte, comment puis-je déclarer correctement une importation étrangère sur une DLL Win32? Je n'ai pas pu trouver quoi que ce soit sur Google. Pour suivre cette question, serai-je capable d'utiliser un programme comme c2hs ou hsc2hs pour analyser le fichier d'en-tête hw-driver.h afin que je n'aie pas à écrire manuellement les appels d'importation étrangers pour tous les 20-25? fonctions contenues dans cette DLL? Je n'ai pas non plus trouvé d'exemples décents de cela.


EDIT: ephemient a souligné que la syntaxe correcte pour la ligne d'importation étrangère est:

foreign import stdcall "hw-driver.h HW_Init" hwInit :: IO CInt 

Avec cela, je suis en mesure d'appeler ghci dlltest.hs -lhw-driver et correctement appeler la fonction principale avec un retour réussi code. Cependant, la commande ghc --make dlltest.hs -lhw-driver échoue avec une erreur de l'éditeur de liens.Alors, voici la sortie prolixe de cette commande (notez que j'ai tous hw-conducteur {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 


Comme il se trouve, la liaison réelle n'était pas aussi difficile que je le pensais être. J'utilisais foreign import stdcall que je pensais être correct avec une DLL construite dans Visual Studio 2003. J'ai dû télécharger l'outil pexports pour MinGW, qui répertorie les fonctions exportées à partir d'une DLL. L'éditeur de liens recherchait HWInit @ 0 depuis le début, mais pexports indiquait que la DLL exportait uniquement HWInit.

je changé ma ligne foreign importccall à la place, et j'ai été avec succès en mesure de lier le programme à l'aide soit de ghc --make dlltest.hs hw-driver.lib ou ghc --make dlltest.hs -L. -lhw-driver en raison d'avoir à la fois le .lib et le fichier .dll dans le répertoire de travail.

Répondre

5

FFI speC# 4.1.1 Import Declarations,

impent → "[static] [CHNAME] [&] [cid]"
                      | "dynamic"
                      | "wrapper"

CHNAME est "C nom d'en-tête", pas "nom de la bibliothèque".

FFI speC# 4.1.4 Specification of Header Files

Un en-tête de C spécifiée dans une déclaration d'importation est toujours inclus par #include "CHNAME". Il n'y a pas de support explicite pour #include <chname> inclusion de style. L'ISO C99 [3] norme garantit que tout chemin de recherche qui serait utilisé pour une #include <CHNAME> est également utilisé pour #include "CHNAME" et il est garanti que ces chemins sont recherchés après tous les chemins qui sont uniques à #include "chname". En outre, nous demandons que chname se termine par .h pour rendre l'analyse de la spécification des entités externes non ambiguë.

Essayez avec un nom d'en-tête appropriée,

foreign import stdcall "hw-driver.h HW_Init" hwInit :: IO CInt 

ou sans nom d'en-tête du tout.

foreign import stdcall "HW_Init" hwInit :: IO CInt 

Votre ligne de commande ne semble pas inclure . comme chemin de recherche de la bibliothèque. Il est très probable que c'est le problème. GHCi inclut par magie . dans le chemin de recherche de 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 sur Windows utilise la liaison dynamique par défaut. Puisque vous avez un .lib, qui est une bibliothèque statique, essayez d'informer l'éditeur de liens que vous voulez une liaison 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é c2hs être le plus facile t o utiliser, mais je ne l'ai jamais essayé sur quelque chose nécessitant stdcall s.

Ce n'est pas que onéreux pour écrire tous les trucs foreign manuellement, s'il y a seulement 25 appels ou plus. J'ai réussi à écrire manuellement les liaisons à libvlc quelques années, pour un petit projet ...

+0

ghci dlltest.hs -lhw-pilote m'a permis de courir la fonction principale dans ghci, mais je vais avoir des problèmes avec la compilation de gcc: C: \ temp \ hs> GHC --make dlltest.hs -lhw-driver Liaison dlltest.exe ... C: \ ghc \ ghc-6.10.3 \ gcc-lib \ ld.exe: impossible de trouver -lhw-driver collect2: ld a renvoyé 1 statut de sortie Ceci est très étrange pour moi puisque cela fonctionne correctement dans ghci. Je vais jouer avec ça encore plus. –

+1

GHCi n'utilise pas ld et implémente son propre éditeur de liens à la place. Plus communément, il existe des situations où une bibliothèque peut être utilisée dans la compilation mais pas interactivement, sans solutions de contournement hackish, mais ce cas inverse semble très probable aussi. Pouvez-vous exécuter avec -v et afficher les commandes intermédiaires exécutées par ghc? – ephemient

+0

Le -L. option change ma sortie de "ne peut pas trouver -lhw-driver" en "référence non définie à 'HW_Init @ 0'", mais aucune de ces suggestions ne m'a permis d'obtenir un lien réussi. Trouvé un problème similaire à http://www.nabble.com/OpenVG:-Linker-errors-with-ghc---make,-but-not-with-ghci--td22321487.html mais pas de réponse utile. Ça pourrait finir par arriver sur les listes de diffusion GHC demain ... on dirait que c'est juste un problème avec les options de l'éditeur de liens quelque part. Je suis sûr que je ne suis pas la première personne à utiliser ld pour créer un lien avec une DLL. –

3

est Ci-dessous un exemple de travail qui appelle [GetComputerName] (http://msdn.microsoft.com/en-us/library/ms724295(VS.85).aspx) 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 

construire avec

ghc --make compname.hs -lkernel32 
+1

Cela code en dur le chemin C: \ windows \ system32 dans l'exécutable de sortie, ce que je ne considère pas souhaitable. – ephemient

+0

Alors s'il vous plaît suggérer une alternative souhaitable! –

+1

Assurez-vous que les chemins de recherche de la bibliothèque sont corrects (bien que ce chemin doive déjà être recherché), puis utilisez '-lkernel32'. – ephemient