如何使getLine接受Unicode字符?

时间:2018-08-02 19:52:16

标签: windows haskell unicode encoding console

运行以下代码时

do line <- getLine
   putStrLn line

getLine >>= putStrLn

然后,在

之后
 getLine >>= putStrLn

输入

µ

一个遇到此输出:

现在,我已经预先尝试过chcp 65001,但是该操作不起作用,并且stdin的编码为utf8

没有putStrLn的检查显示:

 getLine
µ
'\NIL'

我的环境:
Windows 10版本10.0.17134内部版本17134
联想IdeaPad 510-15IKB
BIOS版本LENOVO 3JCN30WW
GHCi v 8.2.2

如何解决?

编辑:具体来说,以下操作序列会导致这种情况:

  1. 打开cmd
  2. 键入chcp 65001
  3. 键入ghci
  4. 键入getLine >>= putStrLn
  5. 键入µ

但是,以下内容却没有:

  1. 搜索ghci
  2. ghci.exe处打开%PROGRAMS%\Haskell Platform\8.2.2\bin
  3. 重复4-5。

注意:%PROGRAMS%不是真实的环境变量。

编辑:根据要求,输出GHC.IO.Encoding.getLocaleEncoding

UTF-8

此外,System.IO.hGetEncoding stdin的输出:

Just UTF-8

(使用chcp 65001时)

编辑:字符为U + 00B5。我使用的是德语键盘,系统语言环境德国,语言设置英语,键盘语言ENG(德语)。

1 个答案:

答案 0 :(得分:4)

控制台输入/输出在Windows上完全损坏,并且已经有一段时间了。这是跟踪与Windows上的IO相关的所有问题的最重要的问题: https://ghc.haskell.org/trac/ghc/ticket/11394

我相信,这两张票最能说明您所遇到的行为:

目前唯一的解决方法是手动使用Windows API来处理控制台输出/输入,这很麻烦。

编辑

所以,为了它的地狱,我决定忍受其中的一些痛苦。 :)

这是下面代码的输出:

         0                                                  1    3
939   2074  Die CSU verlangt von der schwarz-gelben Koalit...  1.0

这绝不是一个完全正确或安全的解决方案,但它确实有效:

====
Input: µ
Output: µ
====

编辑2

@dfeuer要求我列出该答案中不安全,不正确或不完整的内容。我只在Linux上真正编写过代码,所以我不是Windows程序员,但是在我的想法浮现在脑海的是,在将这些代码用于实际程序之前,需要对这些事情进行更改:

  • 最重要的部分是代码只能与控制台句柄一起使用,控制台句柄可以通过GetConsoleMode API调用来确定。
  • 对于其他类型的句柄,上面的代码将不执行任何操作,例如。如果与管道或文件句柄一起使用,则会在编码方面遇到问题,但这是一个完全独立的问题。
  • 未解决API调用失败。因此,我们必须通过查看返回的module Main where import Control.Monad import System.IO import Foreign.Ptr import Foreign.ForeignPtr import Foreign.C.String import Foreign.C.Types import Foreign.Storable import System.Win32 import System.Win32.Types import Graphics.Win32.Misc foreign import ccall unsafe "windows.h WriteConsoleW" c_WriteConsoleW :: HANDLE -> LPWSTR -> DWORD -> LPDWORD -> LPVOID -> IO BOOL foreign import ccall unsafe "windows.h ReadConsoleW" c_ReadConsoleW :: HANDLE -> LPWSTR -> DWORD -> LPDWORD -> LPVOID -> IO BOOL -- | Read n characters from a handle, which should be a console stdin hwGetStrN :: Int -> Handle -> IO String hwGetStrN maxLen hdl = do withCWStringLen (Prelude.replicate maxLen '\NUL') $ \(cstr, len) -> do lpNumberOfCharsWrittenForeignPtr <- mallocForeignPtr withHandleToHANDLE hdl $ \winHANDLE -> withForeignPtr lpNumberOfCharsWrittenForeignPtr $ \lpNumberOfCharsRead -> do c_ReadConsoleW winHANDLE cstr (fromIntegral len) lpNumberOfCharsRead nullPtr numWritten <- peek lpNumberOfCharsRead peekCWStringLen (cstr, fromIntegral numWritten) -- | Write a string to a handle, which should be a console stdout or stderr. hwPutStr :: Handle -> String -> IO () hwPutStr hdl str = do void $ withCWStringLen str $ \(cstr, len) -> do lpNumberOfCharsWrittenForeignPtr <- mallocForeignPtr withHandleToHANDLE hdl $ \winHANDLE -> withForeignPtr lpNumberOfCharsWrittenForeignPtr $ \ lpNumberOfCharsWritten -> c_WriteConsoleW winHANDLE cstr (fromIntegral len) lpNumberOfCharsWritten nullPtr main :: IO () main = do hwPutStr stdout "====\nInput: " str <- hwGetStrN 10 stdin hwPutStr stdout "Output: " hwPutStr stdout str hwPutStr stdout "====\n" 来检查呼叫是否成功,以及何时不使用GetLastError来将错误报告给用户。
  • 上面实现的功能非常有限,没有检查它们实际读/写了多少缓冲区。因此,BOOL只能处理hwGetStrN个字符,因此需要递归调用才能获得类似于n的行为
  • 进行所有健全性检查,例如hGetLineDWORD,因此Word32调用容易受到整数溢出的影响,这既不正确也不安全。
  • 在32位操作系统上,FFI调用必须为fromIntegral len,而stdcallccall,因此需要一些CPP