打印到控制台时无法显示 Unicode 字符(使用双缓冲区)

Trouble getting Unicode character to display when printing to console (using a double buffer)

我在将 unicode 代码传递到我的双缓冲区时遇到了问题。现在我已经尝试单步执行代码,我会指出 unicode 字符在哪里仍然是正确的。 (看画法)

我认为问题出在最后的 "print" 方法中。

快速概述发生的情况:
- 创建缓冲区,允许绘制函数将字符插入缓冲区
- 打印功能会将缓冲区发送到控制台,以便显示

在我提供的示例中,我传入了 unicode 字符“\u2580”,但它打印了 ascii 字符“80”。

使用这个 link:
http://www.kreativekorp.com/charset/font.php?font=Consolas
我可以正确打印出 Basic Latin 和 Latin 1,但没有别的。

经过进一步研究,我认为问题不在于控制台代码页。除了测试切换代码页(没有效果),我仍然可以做 Console.Out.WriteLine("\u2580") 并获得正确的 unicode 字符。

提供一些附加信息...下面是调用的最终函数(作为 print 方法的结果)

[DllImport("kernel32.dll", SetLastError = true)]
        static extern bool WriteConsoleOutput(
          SafeFileHandle hConsoleOutput,
          CharInfo[] lpBuffer,
          Coord dwBufferSize,
          Coord dwBufferCoord,
          ref SmallRect lpWriteRegion);

可在此处找到相关文档:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx
现在我可以保证当传入 lpbuffer 时,它有一个多维数组,其中的第一个值(在这种情况下)由属性和字符组成——此时它仍然正确,使用调试器检查过。

我已经提供了完整的代码以使其成为运行。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.IO;
using System.Text;

namespace DoubleBuffer
{

    ///<summary>
    ///This class allows for a double buffer in Visual C# cmd promt. 
    ///The buffer is persistent between frames.
    ///</summary>
    class buffer
    {
        private int width;
        private int height;
        private int windowWidth;
        private int windowHeight;
        private SafeFileHandle h;
        private CharInfo[] buf;
        private SmallRect rect;

        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern SafeFileHandle CreateFile(
            string fileName,
            [MarshalAs(UnmanagedType.U4)] uint fileAccess,
            [MarshalAs(UnmanagedType.U4)] uint fileShare,
            IntPtr securityAttributes,
            [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
            [MarshalAs(UnmanagedType.U4)] int flags,
            IntPtr template);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool WriteConsoleOutput(
          SafeFileHandle hConsoleOutput,
          CharInfo[] lpBuffer,
          Coord dwBufferSize,
          Coord dwBufferCoord,
          ref SmallRect lpWriteRegion);

        [StructLayout(LayoutKind.Sequential)]
        public struct Coord
        {
            private short X;
            private short Y;

            public Coord(short X, short Y)
            {
                this.X = X;
                this.Y = Y;
            }
        };

        [StructLayout(LayoutKind.Explicit)]
        public struct CharUnion
        {
            [FieldOffset(0)]
            public char UnicodeChar;
            [FieldOffset(0)]
            public byte AsciiChar;
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct CharInfo
        {
            [FieldOffset(0)]
            public CharUnion Char;
            [FieldOffset(2)]
            public short Attributes;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SmallRect
        {
            private short Left;
            private short Top;
            private short Right;
            private short Bottom;
            public void setDrawCord(short l, short t)
            {
                Left = l;
                Top = t;
            }
            public void setWindowSize(short r, short b)
            {
                Right = r;
                Bottom = b;
            }
        }

        /// <summary>
        /// Consctructor class for the buffer. Pass in the width and height you want the buffer to be.
        /// </summary>
        /// <param name="Width"></param>
        /// <param name="Height"></param>
        public buffer(int Width, int Height, int wWidth, int wHeight) // Create and fill in a multideminsional list with blank spaces.
        {
            if (Width > wWidth || Height > wHeight)
            {
                throw new System.ArgumentException("The buffer width and height can not be greater than the window width and height.");
            }
            h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
            width = Width;
            height = Height;
            windowWidth = wWidth;
            windowHeight = wHeight;
            buf = new CharInfo[width * height];
            rect = new SmallRect();
            rect.setDrawCord(0, 0);
            rect.setWindowSize((short)windowWidth, (short)windowHeight);
            Clear();
            Console.OutputEncoding = System.Text.Encoding.Unicode;
        }
        /// <summary>
        /// This method draws any text to the buffer with given color.
        /// To chance the color, pass in a value above 0. (0 being black text, 15 being white text).
        /// Put in the starting width and height you want the input string to be.
        /// </summary>
        /// <param name="str"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="attribute"></param>
        public void Draw(String str, int x, int y, short attribute) //Draws the image to the buffer
        {
            if (x > windowWidth - 1 || y > windowHeight - 1)
            {
                throw new System.ArgumentOutOfRangeException();
            }
            if (str != null)
            {
                Char[] temp = str.ToCharArray(); //From testing I know the unicode character is still correct here

                int tc = 0;
                foreach (Char le in temp)
                {
                    buf[(x + tc) + (y * width)].Char.UnicodeChar = le; //Height * width is to get to the correct spot (since this array is not two dimensions).
                    System.Console.Out.WriteLine(buf[(x + tc) + (y * width)].Char.UnicodeChar); //once again, a simple test to see if the unicode character is working. Enter debugging and you will see the value is correct.
                    if (attribute != 0)
                        buf[(x + tc) + (y * width)].Attributes = attribute;
                    tc++;
                }
            }


        }
        /// <summary>
        /// Prints the buffer to the screen.
        /// </summary>
        public void Print() //Paint the image
        {
            if (!h.IsInvalid)
            {

                bool b = WriteConsoleOutput(h, buf, new Coord((short)width, (short)height), new Coord((short)0, (short)0), ref rect); //This is the point where I think it is messing up, but I am at a loss at to what is happening.
            }
        }
        /// <summary>
        /// Clears the buffer and resets all character values back to 32, and attribute values to 1.
        /// </summary>
        public void Clear()
        {
            for (int i = 0; i < buf.Length; i++)
            {
                buf[i].Attributes = 1;
                buf[i].Char.UnicodeChar = '\u0020';
            }
        }
        /// <summary>
        /// Pass in a buffer to change the current buffer.
        /// </summary>
        /// <param name="b"></param>
        public void setBuf(CharInfo[] b)
        {
            if (b == null)
            {
                throw new System.ArgumentNullException();
            }

            buf = b;
        }

        /// <summary>
        /// Set the x and y cordnants where you wish to draw your buffered image.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void setDrawCord(short x, short y)
        {
            rect.setDrawCord(x, y);
        }

        /// <summary>
        /// Clear the designated row and make all attribues = 1.
        /// </summary>
        /// <param name="row"></param>
        public void clearRow(int row)
        {
            for (int i = (row * width); i < ((row * width + width)); i++)
            {
                if (row > windowHeight - 1)
                {
                    throw new System.ArgumentOutOfRangeException();
                }
                buf[i].Attributes = 0;
                buf[i].Char.UnicodeChar = '\u0020';
            }
        }

        /// <summary>
        /// Clear the designated column and make all attribues = 1.
        /// </summary>
        /// <param name="col"></param>
        public void clearColumn(int col)
        {
            if (col > windowWidth - 1)
            {
                throw new System.ArgumentOutOfRangeException();
            }
            for (int i = col; i < windowHeight * windowWidth; i += windowWidth)
            {
                buf[i].Attributes = 0;
                buf[i].Char.UnicodeChar = '\u0020';
            }
        }

        /// <summary>
        /// This function return the character and attribute at given location.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns>
        /// byte character
        /// byte attribute
        /// </returns>
        public KeyValuePair<byte, byte> getCharAt(int x, int y)
        {
            if (x > windowWidth || y > windowHeight)
            {
                throw new System.ArgumentOutOfRangeException();
            }
            return new KeyValuePair<byte, byte>((byte)buf[((y * width + x))].Char.UnicodeChar, (byte)buf[((y * width + x))].Attributes);
        }
    }
}

运行 的示例是:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DoubleBuffer
{
    class ExampleClass
    {
        static int width = 80;
        static int height = 30;
        public static void Main(string[] args)
        {
            Console.CursorVisible = false;
            Console.Title = "Double buffer example";
            System.Console.SetBufferSize(width, height);
            System.Console.SetWindowSize(width, height);
            Console.Clear();
            buffer myBuf = new buffer(width, height, width, height);
            backgroundbuf.Draw("\u2580", 0, 0, 2);
            myBuf.Print();
            Console.ReadLine();
        }
    }
}

如果我需要提供更多信息,请告诉我! (注意:此代码直接来自我在 msdn 上的代码示例。直到现在才意识到这个错误,我想确保它已修复!!!)

以下链接帮助我解决了这个问题!

此外,Microsoft 开发中心也有一些很好的资源来解决此类问题。

好的!我找到了解决方案!这实际上很简单。首先,我需要摆脱 "CharUnion" 并将其放在 "CharInfo" 中(不知道为什么,但它有效 - 如果您知道如何修复 CharUnion,请 post!) .此外,我需要声明CharSet = CharSet.Auto,否则它默认为ascii。 (向 post 编辑了他们的答案但删除了它的人大声喊叫 - 你的想法是对的!)。 -- 新结构 CharInfo(取代结构 CharUnion):

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
        public struct CharInfo
        {
            [FieldOffset(0)]
            public char UnicodeChar;
            [FieldOffset(0)]
            public byte bAsciiChar;
            [FieldOffset(2)]
            public short Attributes;
        }

接下来,我认为它不会改变任何东西,但我现在没有创建新文件,而是获得了当前输出缓冲区的句柄。这消除了对 SafeFileHandle CreateFile 的需要。

public const Int32 STD_OUTPUT_HANDLE = -11;

        [DllImportAttribute("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern ConsoleHandle GetStdHandle(Int32 nStdHandle);

为了便于使用,对于任何未来的读者,这是我目前使用的代码。请注意,可能有一些随机片段对缓冲区工作没有用,我直接从我正在工作的项目中取出它。不过你应该明白这一点。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.IO;
using System.Text;

namespace DoubleBuffer
{
    /*
     * Copyright [2012] [Jeff R Baker]

     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at    
     * 
     *          http://www.apache.org/licenses/LICENSE-2.0
     * 
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     * 
     * v 1.2.0
     */
    ///<summary>
    ///This class allows for a double buffer in Visual C# cmd promt. 
    ///The buffer is persistent between frames.
    ///</summary>
    class buffer
    {
        private int width;
        private int height;
        private int windowWidth;
        private int windowHeight;
        private ConsoleHandle h;
        private CharInfo[] buf;
        private SmallRect rect;

        public const Int32 STD_OUTPUT_HANDLE = -11;

        [DllImportAttribute("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern ConsoleHandle GetStdHandle(Int32 nStdHandle);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool WriteConsoleOutput(
          ConsoleHandle hConsoleOutput,
          CharInfo[] lpBuffer,
          Coord dwBufferSize,
          Coord dwBufferCoord,
          ref SmallRect lpWriteRegion);

        [StructLayout(LayoutKind.Sequential)]
        public struct Coord
        {
            private short X;
            private short Y;

            public Coord(short X, short Y)
            {
                this.X = X;
                this.Y = Y;
            }
        };



        [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
        public struct CharInfo
        {
            [FieldOffset(0)]
            public char UnicodeChar;
            [FieldOffset(0)]
            public byte bAsciiChar;
            [FieldOffset(2)]
            public short Attributes;
        }


        [StructLayout(LayoutKind.Sequential)]
        public struct SmallRect
        {
            private short Left;
            private short Top;
            private short Right;
            private short Bottom;
            public void setDrawCord(short l, short t)
            {
                Left = l;
                Top = t;
            }
            public short DrawCordX()
            {
                return Left;
            }
            public short DrawCordY()
            {
                return Top;
            }
            public void setWindowSize(short r, short b)
            {
                Right = r;
                Bottom = b;
            }
        }

        /// <summary>
        /// Consctructor class for the buffer. Pass in the width and height you want the buffer to be.
        /// </summary>
        /// <param name="Width"></param>
        /// <param name="Height"></param>
        public buffer(int Width, int Height, int wWidth, int wHeight) // Create and fill in a multideminsional list with blank spaces.
        {
            if (Width > wWidth || Height > wHeight)
            {
                throw new System.ArgumentException("The buffer width and height can not be greater than the window width and height.");
            }
            h = GetStdHandle(STD_OUTPUT_HANDLE);
            width = Width;
            height = Height;
            windowWidth = wWidth;
            windowHeight = wHeight;
            buf = new CharInfo[width * height];
            rect = new SmallRect();
            rect.setDrawCord(0, 0);
            rect.setWindowSize((short)windowWidth, (short)windowHeight);


            Console.OutputEncoding = System.Text.Encoding.Unicode;
            Clear();

        }

        /// <summary>
        /// This method draws any text to the buffer with given color.
        /// To chance the color, pass in a value above 0. (0 being black text, 15 being white text).
        /// Put in the starting width and height you want the input string to be.
        /// </summary>
        /// <param name="str"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="attribute"></param>
        public void Draw(String str, int x, int y, short attribute) //Draws the image to the buffer
        {

            if (x > windowWidth - 1 || y > windowHeight - 1)
            {
                throw new System.ArgumentOutOfRangeException();
            }
            if (str != null)
            {
                Char[] temp = str.ToCharArray();

                int tc = 0;
                foreach (Char le in temp)
                {
                    buf[(x + tc) + (y * width)].UnicodeChar = le; //Height * width is to get to the correct spot (since this array is not two dimensions).

                    if (attribute != 0)
                        buf[(x + tc) + (y * width)].Attributes = attribute;
                    tc++;


                }
            }


        }
        /// <summary>
        /// Prints the buffer to the screen.
        /// </summary>
        public void Print() //Paint the image
        {
            if (!h.IsInvalid)
            {

                bool b = WriteConsoleOutput(h, buf, new Coord((short)width, (short)height), new Coord((short)0, (short)0), ref rect);
            }
        }
        /// <summary>
        /// Clears the buffer and resets all character values back to 32, and attribute values to 1.
        /// </summary>
        public void Clear()
        {
            for (int i = 0; i < buf.Length; i++)
            {
                buf[i].Attributes = 1;
                buf[i].UnicodeChar = '\u0020';
            }
        }
        /// <summary>
        /// Pass in a buffer to change the current buffer.
        /// </summary>
        /// <param name="b"></param>
        public void setBuf(CharInfo[] b)
        {
            if (b == null)
            {
                throw new System.ArgumentNullException();
            }

            buf = b;
        }

        /// <summary>
        /// Set the x and y cordnants where you wish to draw your buffered image.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void setDrawCord(short x, short y)
        {
            rect.setDrawCord(x, y);
        }

        /// <summary>
        /// Clear the designated row and make all attribues = 1.
        /// </summary>
        /// <param name="row"></param>
        public void clearRow(int row)
        {
            for (int i = (row * width); i < ((row * width + width)); i++)
            {
                if (row > windowHeight - 1)
                {
                    throw new System.ArgumentOutOfRangeException();
                }
                buf[i].Attributes = 0;
                buf[i].UnicodeChar = '\u0020';
            }
        }

        /// <summary>
        /// Clear the designated column and make all attribues = 1.
        /// </summary>
        /// <param name="col"></param>
        public void clearColumn(int col)
        {
            if (col > windowWidth - 1)
            {
                throw new System.ArgumentOutOfRangeException();
            }
            for (int i = col; i < windowHeight * windowWidth; i += windowWidth)
            {
                buf[i].Attributes = 0;
                buf[i].UnicodeChar = '\u0020';
            }
        }

        /// <summary>
        /// This function return the character and attribute at given location.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns>
        /// byte character
        /// byte attribute
        /// </returns>
        public KeyValuePair<byte, byte> getCharAt(int x, int y)
        {
            if (x > windowWidth || y > windowHeight)
            {
                throw new System.ArgumentOutOfRangeException();
            }
            return new KeyValuePair<byte, byte>((byte)buf[((y * width + x))].UnicodeChar, (byte)buf[((y * width + x))].Attributes);
        }

        public class ConsoleHandle : SafeHandleMinusOneIsInvalid
        {
            public ConsoleHandle() : base(false) { }

            protected override bool ReleaseHandle()
            {
                return true; //releasing console handle is not our business
            }
        }

        public int X
        {
            get { return width; }
        }

        public int Y
        {
            get { return height; }
        }

        public int dX
        {
            get { return rect.DrawCordX(); }
        }

        public int dY
        {
            get { return rect.DrawCordY(); }
        }
    }
}

对此的任何更改都可以在 MSDN 上的 "Console Double Buffer C#" 上更新。