Вопрос

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

У меня есть библиотека 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-название функции haskell-название функции :: Объявление типа на Haskell.Таким образом, моим должен быть stdcall иностранного импорта (потому что вы используете stdcall при загрузке библиотеки DLL в Win32) "hw-driver" (потому что файл называется "hw-driver.dll " и он расположен в том же каталоге, что и dlltest.hs) "HW_Init" (название функции в C) hwInit ::IO (Cint) (пустые аргументы, возвращающие значение 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), потому что я могу запустить следующий код, который был скопирован из реального мира 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 --}

Итак, короткий вопрос: как мне правильно объявить импорт из-за рубежа в Win32 DLL?Я не смог найти что угодно в Google.

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


Редактировать:ephemient указал, что правильный синтаксис для строки foreign import следующий:

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

Благодаря этому я могу позвонить ghci dlltest.hs -lhw-driver и правильно вызовите основную функцию с успешным кодом возврата.Однако команда ghc --make dlltest.hs -lhw-driver сбой из-за ошибки компоновщика.Итак, вот подробный вывод этой команды (обратите внимание, что у меня есть весь hw-driver.{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-файла.

Это было полезно?

Решение

Спецификация FFI # 4.1.1 Импортные декларации,

надвигающийся → " [static] [имя файла] [&] [уголовный розыск] "
            | " dynamic "
            | " wrapper "

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

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

Заголовок C, указанный в объявлении импорта, всегда включается с помощью #include "имя файла".Явной поддержки для #include <имя файла> включение стиля.Стандарт ISO C99 [3] стандарт гарантирует, что любой путь поиска, который будет использоваться для #include <имя файла> также используется для #include "имя файла" и гарантируется, что поиск по этим путям выполняется после всех путей, уникальных для #include "имя файла".Кроме того, мы требуем, чтобы имя файла заканчивается на .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 самый простой в использовании, но я никогда не пробовал его ни на чем, требующем stdcalls.

Это не так это обременительно писать все эти foreign заполняйте вручную, если всего 25 вызовов или около того.Мне удалось вручную записать привязки к libvlc несколько лет назад, для какого-то небольшого проекта...

Другие советы

Ниже приведен рабочий пример, который вызывает [GetComputerName](http://msdn.microsoft.com/en-us/library/ms724295 (ПРОТИВ 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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top