Haskell Windows 中与 GHCI 的外部函数接口

Haskell Foreign Function Interface with GHCI in Windows

首先,我指定我使用Windows10 64位和Haskell平台8.0.1。

我尝试使用以下代码在 Windows 中使用 Haskell 的 FFI。

import Control.Monad
import Data.Char
import Foreign.C

getCh :: IO Char
getCh = liftM (chr . fromEnum) c_getch
foreign import ccall unsafe "conio.h getch" c_getch :: IO CInt

main :: IO ()
main = getCh >>= \x -> print x

之后用ghc就可以编译好了

> ghc Examples.hs
[1 of 1] Compiling Main             ( Examples.hs, Examples.o )
Linking Examples.exe ...

它完全运行。

> Examples.exe
'1'

(当我在 运行 之后输入 1 时)

但是,GHCI 出现了问题。当我将它加载到 ghci 时,我收到了这些消息。

> ghci Examples.hs
GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main             ( Examples.hs, interpreted )
Ok, modules loaded: Main.
*Main> main

ByteCodeLink: can't find label
During interactive linking, GHCi couldn't find the following symbol:
  getch
This may be due to you not asking GHCi to load extra object files,
archives or DLLs needed by your current session.  Restart GHCi, specifying
the missing library using the -L/path/to/object/dir and -lmissinglibname
flags, or simply by naming the relevant files on the GHCi command line.
Alternatively, this link failure might indicate a bug in GHCi.
If you suspect the latter, please send a bug report to:
  glasgow-haskell-bugs@haskell.org

*Main>

我尝试加载"missing library",例如需要使用conio.h的“-lmsvcrt”,但结果悲观地相同。

> ghci -lmsvcrt Examples.hs
GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main             ( Examples.hs, interpreted )
Ok, modules loaded: Main.
*Main> main

ByteCodeLink: can't find label
During interactive linking, GHCi couldn't find the following symbol:
  getch
This may be due to you not asking GHCi to load extra object files,
archives or DLLs needed by your current session.  Restart GHCi, specifying
the missing library using the -L/path/to/object/dir and -lmissinglibname
flags, or simply by naming the relevant files on the GHCi command line.
Alternatively, this link failure might indicate a bug in GHCi.
If you suspect the latter, please send a bug report to:
  glasgow-haskell-bugs@haskell.org

*Main>

GHCI 可能会加载库,因为当我尝试加载错误的库时,ghci 会打印相关错误。

我尝试了其他几种方法,比如使用 ghci Examples.hs -fobject-codeghci -lmsvcrt Examples.hs -fobject-code,甚至

ghci Examples.hs "-luser32" "-lgdi32" "-lwinmm" "-ladvapi32" "-lshell32"
"-lshfolder" "-lwsock32" "-luser32" "-lshell32" "-lmsvcrt" "-lmingw32" 
"-lmingwex" "-luser32" "-lmingw32" "-lmingwex" "-lm" "-lwsock32" "-lgdi32" "-lwinmm"

ghc Examples.hs -v5 中找到。

遗憾的是,我的 main 没有任何效果,而且我找不到任何其他方法。

P.S。有谁知道Windows中hSetBuffering的使用方法吗(8年前在ghc ticket #2189中发过,不是已经修复了吗?)

这是因为 Windows 上没有 getchgetch 是 POSIX 并且 POSIX 已在 Windows 上弃用。它仍然存在,但功能已移至不同的名称空间(以将根名称空间释放给用户程序)。如您所见,MSDN 表示 getch 已弃用 https://msdn.microsoft.com/en-us/library/ms235446.aspx 并改为使用 _getch

import Control.Monad
import Data.Char
import Foreign.C

getCh :: IO Char
getCh = liftM (chr . fromEnum) c_getch
foreign import ccall unsafe "conio.h _getch" c_getch :: IO CInt

main :: IO ()
main = getCh >>= \x -> print x

编译和解释都可以。

至于为什么它在使用 getch 时编译而不是解释:

MingW-w64 项目从未像 Microsoft 那样删除过时的功能

如此

$ objdump -t /home/Tamar/ghc2/inplace/mingw/x86_64-w64-mingw32/lib/libmsvcr120.a | grep getch
[  7](sec  1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x0000000000000000 getch
[  8](sec  5)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x0000000000000000 __imp_getch
[  7](sec  1)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x0000000000000000 _getch
[  8](sec  5)(fl 0x00)(ty   0)(scl   2) (nx 0) 0x0000000000000000 __imp__getch

getch 被重定向到 _getch 所以他们有两个版本。这是 MSVC++ 和 GCC 不兼容的根源。

但是微软已经删除了它们

>dumpbin /exports C:\Windows\System32\msvcr120.dll | findstr getch
        699  2BA 0006B8B4 _getch = _getch

那么当你 link 反对 msvcrt 时会发生什么:

  1. 在编译模式下,GCC 和 GHC 都会首先选择静态库,它恰好是一个导入库 libmsvcrt.dll.a。这是由于 link er (ld) 的 link 顺序。

  2. 在解释模式下,GHCi 总是 比静态库更喜欢库的动态版本。原因是在重新 link 期间(当您引入新范围或重新加载时必须发生)动态库要快得多,因为我们不必在内部进行重定位和符号解析。还有一些我们仍然没有正确支持的东西,比如弱符号或普通符号,所以出于这些原因,我们更喜欢动态的。

  3. GHCi 8.0.1 不支持导入库。因此,虽然您可以强制 GHCi 使用静态库(只需为 -l 提供全名,例如 -llibmsvcr.a),但它不会工作,因为运行时加载程序不知道如何处理它。然而,当前 GIT master 支持此功能,并且可能会在 8.0.2