Search code examples
windowshaskellffighciconsole-input

Haskell Foreign Function Interface with GHCI in Windows


First of all, I specify that I use Windows 10 64bit and Haskell Platform 8.0.1.

I try to use FFI of Haskell in Windows using following code.

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

After this, I can compile this well with ghc

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

and it runs completely.

> Examples.exe
'1'

(When I type 1 after running it)

However, the problem occurs with GHCI. When I load it to ghci, I got these messages.

> 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:
  [email protected]

*Main>

I try to load "missing library", such as "-lmsvcrt" which needs to use conio.h, but result is pessimistically same.

> 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:
  [email protected]

*Main>

GHCI probably loads the library, since when I try to load a wrong library, ghci prints errors about that.

I try several other things, like using ghci Examples.hs -fobject-code, ghci -lmsvcrt Examples.hs -fobject-code, and even

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

Which was found in ghc Examples.hs -v5.

Sadly, Nothing works for my main, and I can't find any otherway for this.

P.S. Is there anyone knowing how to use hSetBuffering in Windows (It was posted at 8 years ago in ghc ticket #2189. Isn't it fixed?)


Solution

  • This is because there is no getch on Windows. getch is POSIX and POSIX has been deprecated on Windows. It's still around but the functions have been moved to a different namespace (to free up the root namespace to user programs). As you can see MSDN says getch is deprecated https://msdn.microsoft.com/en-us/library/ms235446.aspx and to use _getch instead.

    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
    

    Will work both compiled and interpreted.

    As to the why it works compiled and not interpreted when using getch:

    The MingW-w64 project has never removed deprecated functions as Microsoft has

    As such

    $ 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 is being redirected to _getch and so they have both versions. This is a source of incompatibility between MSVC++ and GCC.

    Microsoft however has removed them

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

    So what happens, when you link against msvcrt:

    1. In compiled mode both GCC and GHC will pick the static library first which happens to be an import lib libmsvcrt.dll.a. This is due to the link order of the linker (ld).

    2. In interpreted mode, GHCi will always prefer the dynamic version of the library over the static one. The reason is during re-linking (which has to happen when you introduce a new scope or reload) dynamic libraries are much faster as we don't have to internally do relocations and symbol resolving. There are also things we still don't support properly like weak symbols or common symbols, so for these reasons we just prefer the dynamic one.

    3. GHCi 8.0.1 does not support import libraries. So while you can force GHCi to use the static library (just give the full name to -l, e.g. -llibmsvcr.a) It won't work because the runtime loader doesn't know what to do with it. This is however supported in the current GIT master and will likely be in 8.0.2