DLL函数上的Haskell外部导入stdcall

Posted

技术标签:

【中文标题】DLL函数上的Haskell外部导入stdcall【英文标题】:Haskell foreign import stdcall on DLL function 【发布时间】:2010-11-04 20:42:42 【问题描述】:

这可能是一个很容易回答的问题,但出于某种原因,我真的很难解决。

我有一个用 C 语言编写的 DLL,用于在协议级别访问硬件,我想编写一个 Haskell 程序来调用其中一些 C 函数。这是相关 C 标头的 sn-p (由于可能的版权问题,名称略有混淆):

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

这已在 Visual Studio 2003 中编译为 DLL,并且我已成功从 C 和 C# 加载 DLL,因此我确信 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"

给我带来麻烦的是国外进口线。据我了解,语法是外来的 (import/export) (ccall/stdcall) library-name C-function-name haskell-function-name :: Haskell 类型声明。所以我的应该是外​​部导入stdcall(因为你在Win32中加载DLL时使用stdcall)“hw-driver”(因为文件名为“hw-driver.dll”并且它与dlltest.hs位于同一目录中) "HW_Init" (C 中函数的名称) hwInit :: IO (Cint) (void arguments, 返回一个 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) 没有任何问题,因为我可以在 ghci 中运行从 Real World Haskell 复制粘贴的以下代码:

-- 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-该DLL中包含25个函数?我也找不到任何像样的例子。


编辑:ehemient 指出,外国导入行的正确语法是:

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_***_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_***_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,我认为它与 Visual Studio 2003 中内置的 DLL 是正确的。我必须下载 MinGW 的 pexports 工具,其中列出了从动态链接库。链接器一直在寻找 HWInit@0,但pexports 说 DLL 只导出了 HWInit。

我将我的行改为foreign import ccall,并且由于同时拥有 .lib 和工作目录中可用的 .dll 文件。

【问题讨论】:

【参考方案1】:

FFI spec # 4.1.1 Import Declarations,

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

其中 chname 是“C 标头名称”,而不是“库名称”。

FFI spec # 4.1.4 Specification of Header Files

在导入声明中指定的 C 标头始终包含在 #include "chname" 中。没有明确支持 #include &lt;chname&gt; 样式包含。 ISO C99 [3] 标准保证将用于#include &lt;chname&gt; 的任何搜索路径也用于#include "chname@ 987654338@ 并保证在所有 #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

如果仍然失败,则可能是静态库引起了问题。不太可能,但是...

Windows 上的 GHC 默认使用动态链接。由于您有一个 .lib,它是一个静态库,请尝试通知链接器您需要静态链接。

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

至于自动生成的绑定,有

Green Card C->Haskell hsc2hs

我发现 c2hs 是最容易使用的,但我从未在需要 stdcalls 的任何东西上尝试过它。

如果只有 25 个左右的调用,手动编写所有 foreign 的东西并不是繁重。几年前,为了一些小项目,我设法手动将绑定写入libvlc...

【讨论】:

ghci dlltest.hs -lhw-driver 允许我在 ghci 中运行主函数,但我在 gcc 中编译时遇到问题:C:\temp\hs>ghc --make dlltest。 hs -lhw-driver Linking dlltest.exe ... C:\ghc\ghc-6.10.3\gcc-lib\ld.exe: 找不到 -lhw-driver collect2: ld returned 1 exit status 这对我来说很奇怪因为它在 ghci 中正常工作。我打算再玩一下。 GHCi 不使用 ld 而是实现自己的链接器。更常见的情况是,库可以用于编译但不能以交互方式使用,没有骇人听闻的解决方法,但这种相反的情况似乎也很可能。你可以用 -v 运行并发布 ghc 运行的中间命令吗? -L。选项将我的输出从“找不到 -lhw-driver”更改为“未定义对 'HW_Init@0' 的引用”,但是这两个建议都没有让我成功链接。在nabble.com/… 发现了一个类似的问题,但没有有用的答案。明天可能最终会进入 GHC 邮件列表……看起来这只是某个地方的链接器选项的问题。我确信我不是第一个使用 ld 链接 DLL 的人。 我几乎是一个只使用 Linux 的用户,GHC 与 .a 和 .so 的链接对我来说完美无缺,所以任何进一步的帮助都是我的猜测。抱歉,希望您能找到答案。 如果不是为了工作,我根本不会使用 Windows :X 你帮我确定了最初的问题,所以我接受你的回答,我会打开一个新的稍后发布关于奇怪的 ld 行为的问题。谢谢。【参考方案2】:

以下是调用 [GetComputerName](http://msdn.microsoft.com/en-us/library/ms724295(VS.85).aspx) from 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

【讨论】:

将 C:\windows\system32 路径硬编码到输出可执行文件中,我认为这是不可取的。 那么请提出一个理想的替代方案! 确保库搜索路径正确(尽管该路径应该已经被搜索),然后使用-lkernel32 感谢您的指点。使用 -Lc:/windows/system32,我得到了大量未定义的引用错误。省略 -L 参数让每个人都开心。亲,我猜。

以上是关于DLL函数上的Haskell外部导入stdcall的主要内容,如果未能解决你的问题,请参考以下文章

delphi7中如何引入外部的dll文件

为 __stdcall 函数指针提供比预期更多的参数

delphi 怎么动态调用dll中的过程

_cdecl 与 _stdcall 区别

dll导出的函数名的问题

DLL编写中extern “C”和__stdcall的作用