2009-06-22 13 views
11

Esta es probablemente una pregunta realmente fácil de responder, pero por alguna razón realmente estoy luchando con ella.Haskell foreign stdcall de importación en la función DLL

Tengo un archivo DLL escrito en C para acceder al hardware a nivel de protocolo, y quiero escribir un programa Haskell que llame a algunas de esas funciones en C. He aquí un fragmento de la cabecera de C correspondiente (con nombres ligeramente ofuscado debido a posibles problemas de derechos de copia):

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

Esto ha sido compilado como un archivo DLL en Visual Studio 2003, y he cargado correctamente el archivo DLL de ambos C y C#, así que estoy seguro de que la DLL funciona bien. La DLL se llama "hw-driver.dll".

A continuación, aquí está el código fuente Haskell sólo para probar si puedo cargar correctamente el archivo DLL y llamar a la función más simple en él:

{-# 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 línea que me está dando problemas es la línea de importación extranjera. Como yo lo entiendo, la sintaxis es extranjera (importación/exportación) (ccall/stdcall) nombre-bibliotecaC-nombre-funciónHaskell-nombre-función :: Haskell declaración de tipo. Así que la mía debe ser stdcall importación extranjera (porque utiliza stdcall al cargar un archivo DLL en Win32) "hw-driver" (porque el archivo se llama "hw-driver.dll" y se encuentra en el mismo directorio que dlltest.hs) "HW_Init" (el nombre de la función en C) hwInit :: IO (Cint) (argumentos vacíos, devolviendo un int).

Sin embargo, cuando trato de ejecutar ghci dlltest.hs, me sale el siguiente resultado:

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

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

línea 8, columna 43 es el primer signo de comillas en HW_Init. Bueno, quizás tenga que poner el nombre de la biblioteca y el nombre de la función en una sola cadena, lo he visto en algunos lugares. Si trato de ejecución que, a continuación, me sale:

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

dlltest.hs:8:23: Malformed entity string 
Failed, modules loaded: none. 

8:23 es el primer signo de comillas de la nueva cadena "HW_Init HW-conductor".

No creo que haya nada malo con mi configuración GHC (6.10.3), porque puedo ejecutar el siguiente código que estaba pegada una copia impresa de 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 --} 

En tanto pregunta breve, ¿cómo declaro correctamente una importación extranjera en una DLL de Win32? No he podido encontrar nada en Google.

Y al tipo de etiqueta a lo largo de esa pregunta, ¿podré usar un programa como c2hs o hsc2hs para analizar el archivo de encabezado hw-driver.h para no tener que escribir manualmente las llamadas de importación foránea para todos los 20-25 funciones contenidas en esa DLL? No he podido encontrar ningún ejemplo decente de eso tampoco.


EDIT: ephemient ha señalado que la sintaxis correcta para la línea de importación extranjera es:

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

Con esto, yo soy capaz de llamar ghci dlltest.hs -lhw-driver y debidamente llamar a la función principal, con un retorno exitoso código. Sin embargo, el comando ghc --make dlltest.hs -lhw-driver falla con un error de enlazador.Por lo tanto, aquí está el resultado detallado de ese comando (tenga en cuenta que tengo todos HW-controlador {DLL, h, lib} en el directorio de trabajo.):

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 


Como resultado, la vinculación real no fue tan difícil como lo imaginaba. Estaba usando foreign import stdcall que creí que era correcto con una DLL integrada en Visual Studio 2003. Tuve que descargar la herramienta pexports para MinGW, que enumera las funciones exportadas desde una DLL. El enlazador había estado buscando HWInit @ 0 todo el tiempo, pero pexports dijo que el DLL estaba exportando solo HWInit.

cambié de línea a foreign importccall lugar, y yo era capaz de enlazar con éxito el programa utilizando cualquiera de ghc --make dlltest.hs hw-driver.libghc --make dlltest.hs -L. -lhw-driver o debido a tener tanto el .lib y el archivo .dll disponibles en el directorio de trabajo.

Respuesta

5

FFI speC# 4.1.1 Import Declarations,

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

donde chname es "nombre de encabezado C", no "nombre de la biblioteca".

FFI speC# 4.1.4 Specification of Header Files

Un encabezado C especificado en una declaración de importación se incluye siempre por #include "chname". No hay soporte explícito para #include <nombre del canal> inclusión del estilo. La norma ISO C99 [3] garantías estándar que cualquier ruta de búsqueda que se utiliza para una chname>#include <también se utiliza para #include "chname" y se garantiza que estos caminos se buscan después de todos los caminos que son únicos para #include "nombrecliente". Además, requerimos que nombre del servidor finalice en .h para que el análisis sintáctico de las entidades externas sea inequívoco.

intento con un nombre de encabezado adecuado,

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

o sin nombre de encabezado en absoluto.

foreign import stdcall "HW_Init" hwInit :: IO CInt 

Su línea de comandos no parece incluir . como una ruta de búsqueda de biblioteca. Es muy probable que este sea el problema. GHCi mágicamente incluye . en la ruta de búsqueda de la biblioteca.

 
ghc --make dlltest.hs -L. -lhwdriver 

Si eso falla, tal vez sea la biblioteca estática la que esté causando problemas. Improbable, pero ...

GHC en Windows utiliza vinculación dinámica por defecto. Como tiene .lib, que es una biblioteca estática, intente informar al vinculador que desea vincular estática.

 
ghc --make dlltest.hs -L. -optl-Bstatic -lhwdriver -optl-Bdynamic 

En cuanto a fijaciones generadas automáticamente, hay

que he encontrado c2hs ser el más fácil de t o uso, pero nunca lo he probado en nada que requiera stdcall s.

No es que es oneroso escribir todas las cosas foreign manualmente, si solo hay 25 llamadas más o menos. Me las arreglé para escribir manualmente fijaciones a libvlc hace unos años, por algún pequeño proyecto ...

+0

ghci dlltest.hs -lhw-conductor me permitió ejecutar la función principal de ghci, pero estoy teniendo problemas con la compilación de gcc: C: \ temp \ hs> GHC --make dlltest.hs -lhw-driver Vinculación dlltest.exe ... C: \ ghc \ ghc-6.10.3 \ gcc-lib \ ld.exe: no se puede encontrar -lhw-driver collect2: ld devuelto 1 estado de salida Esto es muy extraño para mí ya que funciona correctamente en ghci. Voy a jugar con eso un poco más. –

+1

GHCi no usa ld e implementa su propio enlazador en su lugar. Más comúnmente, hay situaciones en las que se puede usar una biblioteca para compilar, pero no interactivamente, sin soluciones provisionales, pero este caso inverso parece bastante probable también. ¿Puedes correr con -v y publicar los comandos intermedios que ejecuta ghc? – ephemient

+0

La -L. option cambia mi salida de "no puedo encontrar -lhw-driver" a "referencia no definida a 'HW_Init @ 0'", pero ninguna sugerencia me consiguió un enlace exitoso. Se encontró un problema similar en http://www.nabble.com/OpenVG:-Linker-errors-with-ghc---make,-but-not-with-ghci--td22321487.html pero no hay respuesta útil. Podría terminar en las listas de correo de GHC mañana ... parece que es solo un problema con las opciones del vinculador en alguna parte. Estoy seguro de que no soy la primera persona en usar ld para vincular con una DLL. –

3

A continuación se muestra un ejemplo que llama [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 

Construir con

ghc --make compname.hs -lkernel32 
+1

Eso codifica la ruta C: \ windows \ system32 en el ejecutable de salida, que no consideraría deseable. – ephemient

+0

¡Entonces sugiera una alternativa deseable! –

+1

Asegúrese de que las rutas de búsqueda de la biblioteca sean correctas (aunque esa ruta ya debería ser buscada), luego use '-lkernel32'. – ephemient

Cuestiones relacionadas