从控制台读取Unicode

16

我正在尝试在C#中从控制台读取Unicode字符串,为了举例,让我们使用这个:

c:\SVN\D³ebugger\src\виталик\Program.cs

起初,我只是尝试使用Console.ReadLine(),它返回了c:\SVN\D3ebugger\src\???????\Program.cs

我尝试将Console.InputEncoding设置为UTF8,如下所示:Console.InputEncoding = Encoding.UTF8,但它返回了c:\SVN\D³ebugger\src\???????\Program.cs,基本上弄乱了字符串的Cyrillic部分。

随机地尝试后,我尝试了这样设置编码:Console.InputEncoding = Encoding.GetEncoding(1251);,这次破坏了³字符,返回了c:\SVN\D?ebugger\src\виталик\Program.cs

到这一点,似乎通过切换InputStream的编码方式,我只能一次获得一种语言。

我还尝试了原生方法,做了类似这样的事情:

// Code
public static string ReadLine()
{
    const uint nNumberOfCharsToRead = 1024;
    StringBuilder buffer = new StringBuilder();

    uint charsRead = 0;
    bool result = ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), buffer, nNumberOfCharsToRead, out charsRead, (IntPtr)0);

    // Return the input minus the newline character
    if (result && charsRead > 1) return buffer.ToString(0, (int)charsRead - 1);
    return string.Empty;
}

// Extern definitions

    [DllImport("Kernel32.DLL", ExactSpelling = true)]
    internal static extern IntPtr GetStdHandle(int nStdHandle);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
    static extern bool ReadConsoleW(IntPtr hConsoleInput, [Out] StringBuilder lpBuffer, 
        uint nNumberOfCharsToRead, out uint lpNumberOfCharsRead, IntPtr lpReserved);

对于非Unicode字符串,这个代码是有效的,但是当我尝试读取Unicode字符串时,应用程序会崩溃。我已经尝试告诉Visual Studio在所有异常(包括本机异常)上中断,但是应用程序仍然会崩溃。

我还发现Microsoft Connect上有一个未解决的bug (链接),似乎现在无法从控制台的InputStream读取Unicode。

值得注意的是,尽管与我的问题不严格相关,但如果将Console.OutputEncoding设置为UTF8,则Console.WriteLine可以很好地打印此字符串。

谢谢!

更新1

我正在寻找.NET 3.5的解决方案

更新2

附上我使用的完整本地代码。


可以使用命名管道代替控制台吗? - Goyuix
如果我找不到解决方案,那可能就是我要做的事情... - VitalyB
3个回答

13

在针对.NET 4 client profile时,这似乎运行良好,但不幸的是,在针对.NET 3.5客户端配置文件时却不能正常工作。确保将控制台字体更改为Lucida Console。
如@jcl所指出的那样,尽管我已经针对.NET 4,但这仅因为我安装了.NET 4.5。

class Program
{
    private static void Main(string[] args)
    {
        Console.InputEncoding = Encoding.Unicode;
        Console.OutputEncoding = Encoding.Unicode;

        while (true)
        {
            string s = Console.ReadLine();

            if (!string.IsNullOrEmpty(s))
            {
                Debug.WriteLine(s);

                Console.WriteLine(s);
            }
        }
    }
}

这里输入图片描述


你是在使用.NET 4.5吗?它在.NET 4.0上无法运行。这一行代码Console.InputEncoding = Encoding.Unicode;会抛出一个异常:"IOException - 参数不正确。" - VitalyB
我已经安装了VS 11 beta和.NET 4.5 beta。但是控制台应用程序使用的是VS 2010和.NET 4客户端框架。我正在使用Windows 7 x64 SP1。 - Phil
我可以确认,当针对.NET 3.5客户端配置文件时,我会遇到与您相同的异常。 - Phil
1
它肯定在.NET 4.0上不起作用,除非您安装了.NET 4.5。您的目标应用程序正在使用更新版本的mscorlib(为此 Microsoft 在 .NET 4.5 的开发人员预览版中奇怪地没有更改版本号,这就是为什么即使您针对 4.0,它也在使用它的原因),该版本明确检查 Unicode 代码页以不调用 SetConsoleCP。检查未包括在 4.0 中的常规 mscorlib.dll 中,这就是为什么它会引发 IOException(当 SetConsoleCP 失败时会这样做)。 - Jcl
嗨,我正在运行这个从属应用程序,但它似乎对我不起作用。 - daniel metlitski

7

以下是一个在.NET 3.5 Client中完全可用的版本:

class Program
{
  [DllImport("kernel32.dll", SetLastError = true)]
  static extern IntPtr GetStdHandle(int nStdHandle);

  [DllImport("kernel32.dll")]
  static extern bool ReadConsoleW(IntPtr hConsoleInput, [Out] byte[]
     lpBuffer, uint nNumberOfCharsToRead, out uint lpNumberOfCharsRead,
     IntPtr lpReserved);

  public static IntPtr GetWin32InputHandle()
  {
    const int STD_INPUT_HANDLE = -10;
    IntPtr inHandle = GetStdHandle(STD_INPUT_HANDLE);
    return inHandle;
  }

  public static string ReadLine()
  {
    const int bufferSize = 1024;
    var buffer = new byte[bufferSize];

    uint charsRead = 0;

    ReadConsoleW(GetWin32InputHandle(), buffer, bufferSize, out charsRead, (IntPtr)0);
    // -2 to remove ending \n\r
    int nc = ((int)charsRead - 2) * 2;
    var b = new byte[nc];
    for (var i = 0; i < nc; i++)
      b[i] = buffer[i];

    var utf8enc = Encoding.UTF8;
    var unicodeenc = Encoding.Unicode;
    return utf8enc.GetString(Encoding.Convert(unicodeenc, utf8enc, b));
  }

  static void Main(string[] args)
  {
    Console.OutputEncoding = Encoding.UTF8;
    Console.Write("Input: ");
    var st = ReadLine();
    Console.WriteLine("Output: {0}", st);
  }
}

enter image description here


如果需要处理大字符串,请更改ReadLine()中的bufferSize。请注意,该缓冲区将使用两倍于字符数的字节数。此外,如果可以使用Linq,可以使用以下代码替换那个丑陋的For循环:var b = buffer.Take(nc).ToArray(); - Jcl
非常好,谢谢!不过,我做了类似的事情(使用ReadConsoleW),但根本行不通。我会检查我做错了什么并更新。 - VitalyB
你可能之后没有转换为UTF8。输入可能没问题,但输出可能有问题(只是猜测)。 - Jcl
感谢您的回答,我发现了我的错误并进行了相应的编辑。以前使用的是 new StringBuilder(),这对 ANSI 来说很好用,但对 Unicode 来说会崩溃。现在当我使用初始大小进行初始化 - new StringBuilder(nNumberOfCharsToRead) 时,它可以正常工作。再次感谢! - VitalyB

0
对于.NET Core,我已经成功地使用了这个变体(逐个按键读取作为Unicode字符):
string ReadLineUnicode()
{
    StringBuilder sb = new StringBuilder();
    ConsoleKeyInfo keyInfo;
    while (true)
    {
        keyInfo = Console.ReadKey();
        if (keyInfo.Key == ConsoleKey.Enter)
            break;

        sb.Append(keyInfo.KeyChar);
    }
    Console.WriteLine();
    return sb.ToString();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接