如何将快速彩色输出写入控制台?

Posted

技术标签:

【中文标题】如何将快速彩色输出写入控制台?【英文标题】:How can I write fast colored output to Console? 【发布时间】:2011-02-14 19:40:50 【问题描述】:

我想了解是否有另一种(更快)使用 C# .net 将文本输出到控制台应用程序窗口的方法,而不是使用简单的 Write BackgroundColorForegroundColor 方法和属性?我了解到每个单元格都有背景颜色和前景色,我想缓存/缓冲/写入比使用上述方法更快。

也许使用 Out 缓冲区有一些帮助,但我不知道如何将颜色编码到流中,如果那是颜色数据所在的位置。

这是一个我想要实现的复古风格的基于文本的游戏,我使用标准颜色和 ascii 字符来布置游戏。

请帮忙:)

更新:

Out 和缓冲区可能不是我需要处理的。似乎有一个控制台拥有的屏幕缓冲区。我不知道如何访问它,也许我只是运气不好,除非我导入一些 dll。

【问题讨论】:

【参考方案1】:

更新:添加了一个示例 如果您准备做一些 P/Invoke 的事情,这可能会有所帮助。

基本上,如果您获得控制台缓冲区的句柄,那么您可以使用标准 Win32 API 来操作缓冲区,甚至在屏幕外构建整个缓冲区并将其 blit 到控制台。

唯一棘手的部分是获取控制台缓冲区的句柄。我没有在 .NET 中尝试过,但在过去的几年里,您可以使用 CreateFile 获取当前控制台的句柄(您需要 P/Invoke 这个)并打开“CONOUT$”然后您可以使用该句柄是返回传递给其他 API。

P/Invoke for CreateFilehttp://www.pinvoke.net/default.aspx/kernel32/CreateFile.html

您可以使用 WriteConsoleOutput 将所有字符及其属性从内存缓冲区移动到控制台缓冲区。http://msdn.microsoft.com/en-us/library/ms687404(VS.85).aspx

您可能可以组合一个不错的库来提供对控制台缓冲区的较低级别的访问。

因为我试图让我的 .NET 再次从头开始,所以我想我会尝试一下,看看我是否可以让它工作。这是一个示例,它将用所有字母 A-Z 填充屏幕并遍历所有前景属性 0-15。我想你会对表演印象深刻。老实说,我没有花太多时间查看这段代码,所以错误检查为零,而且这里或那里可能存在一些小错误,但它应该能让你继续使用其余的 API。

using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

namespace ConsoleApplication1

  class Program
  
    
    [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)]
    static extern bool WriteConsoleOutputW(
      SafeFileHandle hConsoleOutput, 
      CharInfo[] lpBuffer, 
      Coord dwBufferSize, 
      Coord dwBufferCoord, 
      ref SmallRect lpWriteRegion);

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

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

    [StructLayout(LayoutKind.Explicit)]
    public struct CharUnion
    
      [FieldOffset(0)] public ushort 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
    
      public short Left;
      public short Top;
      public short Right;
      public short Bottom;
    


    [STAThread]
    static void Main(string[] args)
    
      SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);

      if (!h.IsInvalid)
      
        CharInfo[] buf = new CharInfo[80 * 25];
        SmallRect rect = new SmallRect()  Left = 0, Top = 0, Right = 80, Bottom = 25 ;

        for (byte character = 65; character < 65 + 26; ++character)
        
          for (short attribute = 0; attribute < 15; ++attribute)
          
            for (int i = 0; i < buf.Length; ++i)
            
              buf[i].Attributes = attribute;
              buf[i].Char.AsciiChar = character;
            
            
            bool b = WriteConsoleOutputW(h, buf,
              new Coord()  X = 80, Y = 25 ,
              new Coord()  X = 0, Y = 0 ,
              ref rect);
          
        
      
      Console.ReadKey();
    
  
  

Unicode 示例

using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace FastConsole

    class Program
    

        [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)]
        static extern bool WriteConsoleOutputW(
          SafeFileHandle hConsoleOutput,
          CharInfo[] lpBuffer,
          Coord dwBufferSize,
          Coord dwBufferCoord,
          ref SmallRect lpWriteRegion);

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

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

        [StructLayout(LayoutKind.Explicit)]
        public struct CharUnion
        
            [FieldOffset(0)] public ushort 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
        
            public short Left;
            public short Top;
            public short Right;
            public short Bottom;
        


        [STAThread]
        static void Main(string[] args)
        
            SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);

            if (!h.IsInvalid)
            
                CharInfo[] buf = new CharInfo[80 * 25];
                SmallRect rect = new SmallRect()  Left = 0, Top = 0, Right = 80, Bottom = 25 ;

                for (ushort character = 0x2551; character < 0x2551 + 26; ++character)
                
                    for (short attribute = 0; attribute < 15; ++attribute)
                    
                        for (int i = 0; i < buf.Length; ++i)
                        
                            buf[i].Attributes = attribute;
                            buf[i].Char.UnicodeChar = character;
                        

                        bool b = WriteConsoleOutputW(h, buf,
                          new Coord()  X = 80, Y = 25 ,
                          new Coord()  X = 0, Y = 0 ,
                          ref rect);
                        Console.ReadKey();
                    
                
            
            Console.ReadKey();
        
    

【讨论】:

非常感谢您的帮助!在一些混乱之后,我现在能够得到一些基本的输出。我使用 GetStdHandle 来获取缓冲区,然后将其传递给我的 CharInfo 矩形数组。接下来会检查你的! 其实GetStdHandle可以代替对CreateFile的调用。 有没有办法支持背景颜色?通过buf[i].Attributes = (short)ConsoleColor.Foreground;,我可以很好地处理前景色。我试过buf[i].Attributes = (short)ConsoleColor.Foreground | (short)ConsoleColor.Background;,但这似乎不起作用。 @EthanBierlein,CHAR_INFO 结构的 Attribute 成员在最低有效字节的低 4 位中具有前景色,在高 4 位中具有背景色。例如,如果您想将上面的代码更改为具有调色板颜色 2 的绿色背景,您可以执行以下操作:buf[i].Attributes = (short)(attribute | (2 &lt;&lt; 4)); @JohnAlexiou - 我有时间玩这个,并对现在应该正确支持 Unicode 的示例进行了一些更改。我所做的两个更改:修复 UnicodeChar 的数据类型并更新代码以使用 WriteConsoleOutputW。我希望这会有所帮助。【参考方案2】:

如果您查看Console 用于更改控制台颜色的属性的实现,它们会从kernel32.dll 委托给SetConsoleTextAttribute 方法。此方法将character attributes 作为输入来设置前景色和背景色。

从几个 MSDN 文档页面中,每个屏幕缓冲区(其中一个控制台有一个)都有一个字符信息记录的二维数组,每个都由 CHAR_INFO 表示。这决定了每个字符的颜色。您可以使用SetConsoleTextAttribute 方法对其进行操作,但这适用于写入控制台的任何新文本 - 您无法在控制台上操作现有文本。

除非控制台文本颜色属性中存在较低级别的挂钩(看起来不太可能),否则我认为您无法使用这些方法。


您可以尝试的一件事是创建一个新的屏幕缓冲区,写入该缓冲区,然后使用SetConsoleActiveScreenBuffer 将其切换为控制台的当前缓冲区。这可能会产生更快的输出,因为您会将所有输出写入非活动缓冲区。

【讨论】:

我尝试使用 SetConsoleActiveScreenBuffer 但效果不佳。闪烁没有减少,执行时间增加了一倍。稍后我会再次检查我的实现,也许 CreateConsoleScreenBuffer 中的设置有问题...【参考方案3】:

我成功使用了

using (var stdout = Console.OpenStandardOutput(Cols * Rows))

    // fill
    stdout.Write(buffer, 0, buffer.Length);
    // rinse and repeat

但如果有人能告诉我如何编写扩展 ASCII 码,我将不胜感激

【讨论】:

控制台支持UTF-8,设置Console.OutputEncoding = System.Text.Encoding.UTF8;当你的程序启动时。对于使用 WriteConsoleOutput() 或 WriteConsole() 的任何人,请将 CharSet = CharSet.Unicode 属性添加到 API 调用中。

以上是关于如何将快速彩色输出写入控制台?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ANSI Escape 代码在控制台上输出彩色文本

如何通过 Blazor WebAssembly 写入浏览器控制台?

Spring Boot 集成 Logback 日志:控制台彩色日志输出 + 日志文件输出

python通过colorama模块在控制台输出彩色文字的方法

为啥 termcolor 在 Windows 控制台中输出控制字符而不是彩色文本?

控制台输出彩色样式