Haskell Fremd import Stdcall auf die DLL -Funktion
Frage
Dies ist wahrscheinlich eine sehr einfache Frage, die zu beantworten ist, aber aus irgendeinem Grund habe ich wirklich Probleme damit.
Ich habe eine DLL in C geschrieben, um auf Protokollebene auf Hardware zuzugreifen, und ich möchte ein Haskell -Programm schreiben, das einige dieser C -Funktionen aufruft. Hier ist ein Ausschnitt des relevanten C -Headers (mit Namen, die aufgrund möglicher Urheberrechtsprobleme nur geringfügig verschleiert werden):
#ifdef HWDRIVER_EXPORTS
#define HWDRIVER_API __declspec(dllexport)
#else
#define HWDRIVER_API __declspec(dllimport)
#endif
HWDRIVER_API int HW_Init(void);
Dies wurde als DLL in Visual Studio 2003 zusammengestellt, und ich habe die DLL sowohl von C als auch C#erfolgreich geladen, daher bin ich zuversichtlich, dass die DLL einwandfrei funktioniert. Die DLL heißt "hw-driver.dll".
Als nächstes ist hier der Haskell -Quellcode, um zu testen, ob ich die DLL ordnungsgemäß laden und die einfachste Funktion darin aufrufen kann:
{-# 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"
Die Linie, die mir Ärger macht, ist die fremde Importlinie. Soweit ich es verstehe, ist die Syntax fremd (Import/Export) (ccall/stdcall) Bibliotheksname C-Funktionsname Haskell-Funktionsname :: Haskell -Typ Deklaration. Mine sollte also fremd importieren stdcall (weil Sie STDCall beim Laden einer DLL in Win32 verwenden) "HW-Fahrer" (weil die Datei "hw-driver.dll" bezeichnet wird und im selben Verzeichnis wie DllTest.HS) liegt. "Hw_init" (der Name der Funktion in c) hwinit :: io (cint) (void Argumente, zurückgegeben, ein int).
Wenn ich jedoch versuche zu rennen ghci dlltest.hs
, Ich bekomme die folgende Ausgabe:
[1 of 1] Compiling Main ( dlltest.hs, interpreted )
dlltest.hs:8:43: parse error on input `"'
Failed, modules loaded: none.
Zeile 8, Spalte 43 ist das erste Anführungszeichen für HW_INIT. Okay, vielleicht muss ich sowohl den Bibliotheksnamen als auch den Funktionsnamen in eine Zeichenfolge einfügen, das habe ich an einigen Stellen gesehen. Wenn ich versuche, das auszuführen, dann bekomme ich:
[1 of 1] Compiling Main ( dlltest.hs, interpreted )
dlltest.hs:8:23: Malformed entity string
Failed, modules loaded: none.
8:23 ist das erste Anführungszeichen der neuen Zeichenfolge "HW-Fahrer hw_init".
Ich glaube nicht, dass mit meinem GHC-Setup (6.10.3) etwas falsch ist, da ich den folgenden Code ausführen kann, der in GHCI von Real World Haskell kopiert wurde:
{-- 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 --}
Kurz gesagt, wie lange erkläre ich einen ausländischen Import für eine Win32 -DLL richtig? Ich konnte nicht finden irgendetwas auf Google.
Und um diese Frage mitzuteten, kann ich ein Programm wie C2HS oder HSC2HS verwenden, um die Header -Datei zu analysieren hw-driver.h
Ich muss also nicht manuell die fremden Importanforderungen für alle 20 bis 25 in dieser DLL enthaltenen Funktionen schreiben? Ich konnte auch keine anständigen Beispiele dafür finden.
EDIT: EHEMIENT hat darauf hingewiesen, dass die richtige Syntax für die Fremdimportlinie lautet:
foreign import stdcall "hw-driver.h HW_Init" hwInit :: IO CInt
Damit kann ich anrufen ghci dlltest.hs -lhw-driver
und rufen Sie die Hauptfunktion richtig mit einem erfolgreichen Rückgaberocode auf. Der Befehl jedoch ghc --make dlltest.hs -lhw-driver
fällt mit einem Linker -Fehler fehl. Hier ist also die ausführliche Ausgabe dieses Befehls (beachten Sie, dass ich ganz HW-Fahrer habe. {Dll, h, lib} im Arbeitsverzeichnis):
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
Wie sich herausstellte, war die tatsächliche Verknüpfung nicht so schwierig, wie ich es ausschaffte. Ich habe benutzt
foreign import
stdcall
was ich für korrekt mit einer DLL in Visual Studio 2003 korrekt war. Ich musste die herunterladen pexports
Tool für Mingw, das die von einer DLL exportierten Funktionen auflistet. Der Linker hatte die ganze Zeit nach hwinit@0 gesucht, aber pexports
sagte, die DLL exportierte nur Hwinit.
Ich änderte meine Linie in foreign import
ccall
Stattdessen konnte ich erfolgreich das Programm mit einem von verknüpfen ghc --make dlltest.hs hw-driver.lib
oder ghc --make dlltest.hs -L. -lhw-driver
Aufgrund der im Arbeitsverzeichnis verfügbaren .lib- und der .dll -Datei.
Lösung
FFI Spec # 4.1.1 Importdeklarationen,
impent → " [
static
] [Chname] [&] [cid] "
| "dynamic
"
| "wrapper
"
wo Chname ist "C Headername", nicht "Bibliotheksname".
FFI Spec # 4.1.4 Spezifikation von Header -Dateien
Der in einer Einfuhrerklärung angegebene Wechselstromkopf ist immer von enthalten von
#include "
Chname"
. Es gibt keine explizite Unterstützung für#include <
Chname>
Stileinbeziehung. Die ISO C99 [3] Standard garantiert, dass jeder Suchpfad, der für a verwendet wird#include <
Chname>
wird auch für verwendet#include "
Chname"
und es ist garantiert, dass diese Pfade nach allen Wegen durchsucht werden, die einzigartig sind#include "
Chname"
. Darüber hinaus benötigen wir das Chname endet auf.h
Analyse der Spezifikation externer Einheiten eindeutig.
Versuchen Sie es mit einem richtigen Headernamen,
foreign import stdcall "hw-driver.h HW_Init" hwInit :: IO CInt
oder ohne Kopfzeilenname.
foreign import stdcall "HW_Init" hwInit :: IO CInt
Ihre Befehlszeile scheint nicht einzuschließen .
als Bibliothekssuchpfad. Es ist höchstwahrscheinlich, dass dies das Problem ist. GHCI beinhaltet auf magische Weise .
im Bibliothekssuchpfad.
ghc --make dlltest.hs -L. -lhwdriver
Wenn das noch fehlschlägt, ist es vielleicht die statische Bibliothek, die Probleme verursacht. Unwahrscheinlich, aber ...
GHC auf Windows verwendet standardmäßig eine dynamische Verknüpfung. Da hast du eine .lib
, Das ist eine statische Bibliothek, die versuchen, den Linker zu informieren, den Sie möchten, dass Sie eine statische Verknüpfung haben möchten.
ghc --make dlltest.hs -L. -optl-Bstatic -lhwdriver -optl-Bdynamic
Bei automatisch generierten Bindungen gibt es
Ich habe festgestellt stdcall
s.
Es ist nicht das belastend, alle zu schreiben foreign
Sachen manuell, wenn es nur 25 Anrufe gibt oder so. Ich habe es geschafft, Bindungen manuell zu schreiben libvlc
Vor ein paar Jahren für ein kleines Projekt ...
Andere Tipps
Im Folgenden finden Sie ein funktionierendes Beispiel, das aufgerufen wird [getComputername] (http://msdn.microsoft.com/en-us/library/ms724295(vs.85).aspx) aus 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
Bauen es mit
ghc --make compname.hs -lkernel32