51

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.

Statement
  • 3,888
  • 3
  • 36
  • 45

3 Answers3

60

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$", 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$", 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();
        }
    }
}
Chris Taylor
  • 52,623
  • 10
  • 78
  • 89
  • Thanks alot for your help! I was just able to now get some basic output going after some messing around. I use GetStdHandle to obtain the buffer, then I just pass it my rectangular array of CharInfo. Will check yours out next! – Statement May 02 '10 at 20:55
  • 2
    Actually, GetStdHandle can replace the call to CreateFile. – Chris Taylor May 02 '10 at 21:06
  • Is there a way to support background colors with this? I can do foreground colors just fine by doing `buf[i].Attributes = (short)ConsoleColor.Foreground;`. I've tried doing `buf[i].Attributes = (short)ConsoleColor.Foreground | (short)ConsoleColor.Background;`, but this doesn't seem to work. – Ethan Bierlein Oct 08 '16 at 18:57
  • 4
    @EthanBierlein, the Attribute member of the CHAR_INFO structure has the foreground color in the lower 4 bits and the background color in the upper 4 bits of the least significant byte. For example if you want to change the code above to have a green background which is palette color 2, you can do the following: `buf[i].Attributes = (short)(attribute | (2 << 4));` – Chris Taylor Oct 09 '16 at 01:48
  • @ChrisTaylor Thanks! – Ethan Bierlein Oct 09 '16 at 02:32
  • How can I draw the box characters from codepage 437? Calling SetConsoleOutputCP(437) first doesn't seem to work? https://en.wikipedia.org/wiki/Box-drawing_character#DOS – CodeOrElse Nov 04 '21 at 14:54
  • Great post. I wonder if `Unicode` can be supported somehow. Is there a call to `WriteConsoleOutputW()` that can be made instead? Or some other way to support wide characters output? – John Alexiou Nov 04 '21 at 18:26
  • @JohnAlexiou, thanks. I have not tried it but in theory you can set the `UnicodeChar` to set a unicode character. Note, that this is quite an old post, Microsoft recommends using the VT Sequences https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences – Chris Taylor Nov 04 '21 at 23:16
  • @ChrisTaylor - setting `Console.OutputEncoding = Encoding.Unicode` I was able to get support for some characters (up to code 287 oddly, and then they repeat). [screenshot here](https://i.imgur.com/l7M2V6m.png). I am using `char.ConvertFromUtf32(code)` to generate the characters to be assigned to `CharUnion.UnicodeChar` – John Alexiou Nov 04 '21 at 23:37
  • 1
    @JohnAlexiou - I got a minute to play with this and made some changes to the sample that should now correctly support Unicode. The two changes I made: fix the data type for the UnicodeChar and update the code to use WriteConsoleOutputW. I hope that helps. – Chris Taylor Nov 08 '21 at 18:21
  • @ChrisTaylor - this is great! Thank you. Do you have a suggestion on how to convert `char` to `CharUnion` now? maybe add an `implicit operator CharUnion(char c)` to make it easier to output strings. – John Alexiou Nov 08 '21 at 21:44
  • @JohnAlexiou - If I understand what you are asking correctly, you can assign a char directly to the UnicodeChar member eg. `buf[i].Char.UnicodeChar = '♪';` should work. Btw. that is unicode U+266a – Chris Taylor Nov 09 '21 at 00:11
  • I am not on a Windows machine at the moment, so I could not test the full example, but here is a simple example that shows how you can add implicit conversion operators that will make `CharUnion` interchangeable with `char` : https://gist.github.com/taylorza/a171e055ef8eb61ada471887d9de5646 – Chris Taylor Nov 09 '21 at 00:37
  • @ChrisTaylor - I realized that C# does an implicit conversion from `char` to `ushort`. – John Alexiou Nov 09 '21 at 00:50
5

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.

adrianbanks
  • 81,306
  • 22
  • 176
  • 206
  • I tried using SetConsoleActiveScreenBuffer but I got poor results. No decrease in flickering and execution time doubled. I will check my implementation again later, perhaps some issue with settings in CreateConsoleScreenBuffer... – CodeOrElse May 04 '18 at 12:40
2

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

  • The console supports UTF-8, set Console.OutputEncoding = System.Text.Encoding.UTF8; when your program starts up. For anyone using WriteConsoleOutput() or WriteConsole() add the CharSet = CharSet.Unicode attribute to the API call. – Tim Nov 15 '20 at 00:49