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

发布于 2024-08-30 22:04:17 字数 458 浏览 1 评论 0原文

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

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

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

请帮忙:)

更新:

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

I want to learn if there is another (faster) way to output text to the console application window using C# .net than with the simple Write, BackgroundColor and ForegroundColor methods and properties? I learned that each cell has a background color and a foreground color, and I would like to cache/buffer/write faster than using the mentioned methods.

Maybe there is some help using the Out buffer, but I don't know how to encode the colors into the stream, if that is where the color data resides.

This is for a retrostyle textbased game I am wanting to implement where I make use of the standard colors and ascii characters for laying out the game.

Please help :)

Update:

The Out and buffer is probably not what I need to mess around with. There seems to be a screen buffer that is owned by the console. I don't know how to access it, maybe I am just out of luck unless I import some dlls.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

何以笙箫默 2024-09-06 22:04:17

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

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

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

CreateFile 的 P/Invoke
http://www.pinvoke.net/default.aspx/kernel32/CreateFile.html

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

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

因为我正在尝试让我的 .NET 再次恢复正常,所以我想我应该尝试一下,看看是否可以让它工作。下面是一个示例,它将用所有字母 AZ 填充屏幕并遍历所有前景属性 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();
    }
  }
}  

统一码示例

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();
        }
    }
}

Update: added a sample
If you are prepared to do some P/Invoke stuff, this might help.

Basically if you get a handle to the console buffer, then you can use the standard Win32 APIs wot manipulate the buffer, even build the the entire buffer off screen and the blit it to the Console.

The only tricky part is getting the handle to the console buffer. I have not tried this in .NET, but in years gone by, you could get the handle to the current console by using CreateFile (you will need to P/Invoke this) and open "CONOUT$" then you can use the handle that is return to pass to the other APIs.

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

And you can use WriteConsoleOutput to move all the characters and their attributes from a memory buffer to the console buffer.
http://msdn.microsoft.com/en-us/library/ms687404(VS.85).aspx

You could probably put together a nice library to provide lower-level access to the console buffer.

Since I am trying to get my .NET up to scratch again I thought I would try my hand at this and see if I could get it to work. Here is a sample that will fill the screen with all the letters A-Z and run through all the forground attributes 0-15. I think you will be impressed with the performance. I'll be honest, I did not spend much time reviewing this code so error checking is zero and there might be a little bug here or there but it should get you going with the rest of the APIs.

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
quot;, 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 example

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
quot;, 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();
        }
    }
}
第几種人 2024-09-06 22:04:17

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

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

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


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

If you look at the implementation of Console's properties for altering console colours, they delegate to the SetConsoleTextAttribute method from kernel32.dll. This method takes character attributes as input to set both the foreground and background colours.

From several MSDN doc pages, each screen buffer (of which a console has one) has a two-dimensional array of character info records, each represented by a CHAR_INFO. This is what determines the colour of each character. You can manipulate this using the SetConsoleTextAttribute method, but this is applied to any new text that is written to the console - you cannot manipulate existing text already on the console.

Unless there is a lower-level hook into the console text colour properties (which doesn't look likely), I think you are stuck using these methods.


One thing you could try is to create a new screen buffer, write to that, and then switch it to be the console's current buffer using SetConsoleActiveScreenBuffer. This may yield faster output as you will be writing all output to an inactive buffer.

白日梦 2024-09-06 22:04:17

我成功使用了

using (var stdout = Console.OpenStandardOutput(Cols * Rows))
{
    // fill
    stdout.Write(buffer, 0, buffer.Length);
    // rinse and repeat
}

但是如果有人可以建议我如何编写扩展 ASCII 对此我将不胜感激

I had success using

using (var stdout = Console.OpenStandardOutput(Cols * Rows))
{
    // fill
    stdout.Write(buffer, 0, buffer.Length);
    // rinse and repeat
}

But if anyone can advise me on how I write extended ASCII to this i'd be grateful

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文