代码之家  ›  专栏  ›  技术社区  ›  Mark Rushakoff

对dll函数执行haskell外部导入stdcall

  •  11
  • Mark Rushakoff  · 技术社区  · 16 年前

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

    我有一个用C编写的DLL来访问协议级别的硬件,我想编写一个调用其中一些C函数的haskell程序。下面是相关C头的一个片段(由于可能的版权问题,名称稍微模糊了一点):

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

    给我带来麻烦的是外国进口线。据我所知,语法是外部的(导入/导出)(ccall/stdcall) 库名 C函数名 Haskell函数名 :: Haskell类型声明 . 因此,mine应该是foreign import stdcall(因为在win32中加载dll时使用stdcall)“hw driver”(因为文件名为“hw driver.dll”,并且它与dlltest.hs)“hw_in it”(c中的函数名)hw in it::io(c int)(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)没有任何问题,因为我可以运行以下代码,这些代码是从GHCI中的真实世界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上正确声明一个外部导入?我找不到 任何东西 在谷歌上。

    为了在这个问题上做些标记,我可以使用像c2hs或hsc2hs这样的程序来解析头文件吗? hw-driver.h 所以我不必手动为该dll中包含的所有20-25个函数编写外部导入调用?我也找不到任何像样的例子。


    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 我相信在Visual Studio 2003中构建的DLL是正确的。我必须下载 pexports 用于mingw的工具,它列出从dll导出的函数。链接器一直在寻找hwinit@0,但是 出口商品 说dll只是导出hwinit。

    我把电话改成了 外国进口 ccall 相反,我成功地使用 ghc --make dlltest.hs hw-driver.lib ghc --make dlltest.hs -L. -lhw-driver 因为工作目录中同时有.lib和.dll文件。

    2 回复  |  直到 14 年前
        1
  •  5
  •   ephemient    16 年前

    FFI spec # 4.1.1 Import Declarations ,

    撞击物 “[” static ] 金纳姆 [&;] CID
    dynamic
    wrapper

    哪里 金纳姆 是“c header name”,而不是“library name”。

    FFI spec # 4.1.4 Specification of Header Files

    导入声明中指定的C头始终包含在 #include " 金纳姆 " . 没有明确的支持 #include < 金纳姆 > 风格包容性。ISO C99〔标准〕 3 ]标准保证将用于 包括: 金纳姆 > 也用于 “包括” 金纳姆 并且保证在所有唯一的路径之后搜索这些路径 “包括” 金纳姆 . 此外,我们要求 金纳姆 结束于 .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
    

    对于自动生成的绑定,有

    我发现C2H是最容易使用的,但我从未尝试过任何需要 stdcall S.

    它不是 那个 写下所有的 foreign 手动操作,如果只有25个电话左右。我设法手动将绑定写入 libvlc 几年前,为了一些小项目…

        2
  •  3
  •   Greg Bacon    16 年前

    下面是一个调用[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