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

时间:2015-09-14 16:45:55

标签: c# unicode buffer console-application dllimport

将unicode代码传递到双缓冲区时,我遇到了问题。现在我已经尝试逐步执行代码,并且我将指出unicode字符仍然正确的位置。 (看看绘制方法)

我认为这个问题存在于最终的“印刷”方法中。

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

从我提供的示例中,我传入了unicode字符'\ u2580',但它打印出ascii字符'80'。

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

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

提供一些额外信息......下面是调用的最终函数(作为打印方法的结果)

[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时,它有一个多维数组,其中第一个值(在本例中)由属性组成,而字符 - 此时IS仍然正确,使用调试器进行检查。

我提供了完整的代码以允许它运行。

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上的代码示例。直到现在才知道这个bug,我想确保这个是修复的!)

1 个答案:

答案 0 :(得分:0)

以下是帮助我解决此问题的链接!

此外,Microsoft开发人员中心还提供了一些很好的资源来解决这类问题。

OK!我找到了解决方案!它实际上非常简单。首先,我需要摆脱“CharUnion”并把它放在“CharInfo”中(不知道为什么,但它有效 - 如果你知道如何修复CharUnion,请发帖!)。另外,我需要声明CharSet = CharSet.Auto,否则它默认为ascii。 (向那些发布回答但却删除它的人大声说出来 - 你有正确的想法!)。 - 新结构CharInfo(替换struct 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#”中更新。