Haskell импортный импорт stdcall на функцию DLL

Это, наверное, очень простой вопрос для ответа, но по какой-то причине я действительно борюсь с ним.

У меня есть DLL, написанная на C для доступа к оборудованию на уровне протокола, и я хочу написать программу Haskell, которая вызывает некоторые из этих C-функций. Вот фрагмент соответствующего заголовка C (с именами, которые слегка запутаны из-за возможных проблем с авторским правом):

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

Это было скомпилировано как DLL в Visual Studio 2003, и я успешно загрузил DLL из C и С#, поэтому я уверен, что DLL работает нормально. DLL называется "hw-driver.dll".

Далее, здесь исходный код Haskell просто для проверки, могу ли я правильно загрузить DLL и вызвать в нем простейшую функцию:

{-# 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"

Линия, которая вызывает у меня проблемы, - это импортная линия импорта. Насколько я понимаю, синтаксис является иностранным (импорт/экспорт) (ccall/stdcall) имя-библиотеки имя C-function-name haskell-function-name: объявление типа Haskell. Так что мой должен быть импортным stdcall (потому что вы используете stdcall при загрузке DLL в Win32) "hw-driver" (потому что файл называется "hw-driver.dll", и он находится в том же каталоге, что и dlltest.hs) "HW_Init" (имя функции в C) hwInit:: IO (Cint) (аргументы void, возвращающие int).

Однако, когда я пытаюсь запустить ghci dlltest.hs, я получаю следующий вывод:

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

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

Строка 8, столбец 43 - это первый кавычек на HW_Init. Хорошо, поэтому, возможно, мне нужно указать имя библиотеки и имя функции в одну строку, я видел это в нескольких местах. Если я попытаюсь запустить это, я получаю:

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

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

8:23 - это первый кавычек новой строки "hw-driver HW_Init".

Я не верю, что что-то не так с моей установкой ghc (6.10.3), потому что я могу запустить следующий код, который был скопирован из Real World Haskell в 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 --}

Короче говоря, как правильно объявить внешний импорт в DLL Win32? Я не смог найти что-либо в Google.

И в какой-то тег по этому вопросу я смогу использовать такую ​​программу, как c2hs или hsc2hs, для синтаксического анализа файла заголовка hw-driver.h, поэтому мне не нужно вручную писать внешние импортные вызовы для всех 20- 25 функций, содержащихся в этой DLL? Я тоже не смог найти достойных примеров.


EDIT: ephemient указал, что правильный синтаксис для внешней строки импорта:

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

При этом я могу вызвать ghci dlltest.hs -lhw-driver и правильно вызвать основную функцию с успешным кодом возврата. Однако команда ghc --make dlltest.hs -lhw-driver терпит неудачу с ошибкой компоновщика. Итак, вот подробный вывод этой команды (обратите внимание, что у меня есть весь hw-драйвер. {Dll, h, lib} в рабочем каталоге):

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

<ч/" > Как оказалось, фактическое соединение было не таким сложным, как я это делал. Я использовал foreign import stdcall, который, как я полагал, был прав с помощью библиотеки DLL, встроенной в Visual Studio 2003. Мне пришлось загрузить инструмент pexports для MinGW, в котором перечислены функции, экспортированные из DLL. Линкер все время искал HWInit @0, но pexports сказал, что DLL экспортирует только HWInit.

Я изменил свою строку на foreign import ccall, и я смог успешно связать программу, используя либо ghc --make dlltest.hs hw-driver.lib, либо ghc --make dlltest.hs -L. -lhw-driver из-за наличия как .lib, так и файл .dll, доступный в рабочем каталоге.

Ответ 1

Спецификация FFI 4.1.1 Импорт объявлений,

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

где chname - это "имя заголовка C", а не "имя библиотеки".

Спецификация FFI # 4.1.4 Спецификация файлов заголовков

Заголовок C, указанный в объявлении импорта, всегда включается #include " chname ". Нет явной поддержки включения стиля #include < chname >. Стандарт ISO C99 [3] гарантирует, что будет использоваться любой путь поиска, который будет использоваться для #include < chname > для #include " chname ", и гарантируется, что эти пути будут найдены после всех путей, которые уникальны для #include " chname ". Кроме того, мы требуем, чтобы chname заканчивался на .h, чтобы сделать разбор спецификации внешних объектов однозначной.

Попробуйте указать правильное имя заголовка,

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

или без имени заголовка вообще.

foreign import stdcall "HW_Init" hwInit :: IO CInt

В командной строке не отображается . как путь поиска библиотеки. Скорее всего, это проблема. GHCi магически включает . в пути поиска библиотеки.

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

Если это все еще не удается, возможно, это статическая библиотека, которая вызывает проблемы. Вряд ли, но...

GHC в Windows по умолчанию использует динамическую компоновку. Поскольку у вас есть .lib, который является статической библиотекой, попробуйте сообщить компоновщику, что вы хотите статической привязки.

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

Что касается автоматически сгенерированных привязок, есть

Я нашел c2hs самым простым в использовании, но я никогда не пробовал его ни на что требующее stdcall s.

Не так уж сложно записывать все вещи foreign вручную, если есть только 25 вызовов или около того. Мне удалось вручную записать привязки к libvlc несколько лет назад, для небольшого проекта...

Ответ 2

Ниже приведен рабочий пример, который вызывает [GetComputerName] (http://msdn.microsoft.com/en-us/library/ms724295(VS.85).aspx) от 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

Постройте его с помощью

ghc --make compname.hs -lkernel32