I actually ended up implementing the low level WriteConsoleOutput some time ago as part of a terminal based Tetris implementation I wrote, since doing coloured console output with Console.BackgroundColor
and Console.Write
, is far too slow for full screen refreshes. Doing the raw buffer output is much faster.
Note, this writes text to a location on the screen (eg 5,10), but doesn't update or keep track of the cursor position on it's own - writing with this method will update the text buffer and display the output, but the cursor won't move. You'll need to move and keep track of the console cursor manually with some other method, which shouldn't be too difficult.
Here's my code:
Main interop class:
public static class LowLevelConsole {
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private 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)]
private static extern bool WriteConsoleOutput(
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 char UnicodeChar;
[FieldOffset(0)]
public byte AsciiChar;
}
[StructLayout(LayoutKind.Explicit)]
public struct CharInfo {
[FieldOffset(0)]
public CharUnion Char;
[FieldOffset(2)]
public ushort Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct SmallRect {
public short Left;
public short Top;
public short Right;
public short Bottom;
}
[STAThread]
public static void Write(string line, CharacterAttribute attribute, short xLoc, short yLoc) {
SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
short writeHeight = 1;
short writeWidth = (short)line.Length;
if (!h.IsInvalid) {
CharInfo[] buf = new CharInfo[writeWidth * writeHeight];
SmallRect rect = new SmallRect() { Left = xLoc, Top = yLoc, Right = (short)(writeWidth + xLoc), Bottom = (short)(writeHeight + yLoc) };
for (int i = 0; i < writeWidth; i++) {
buf[i].Attributes = (ushort)attribute;
buf[i].Char.UnicodeChar = line[i];
}
bool b = WriteConsoleOutput(h, buf, new Coord() { X = writeWidth, Y = writeHeight }, new Coord() { X = 0, Y = 0 }, ref rect);
}
}
[STAThread]
public static bool WriteBuffer(CharInfo[,] buffer) { // returns true of success
SafeFileHandle h = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
if (!h.IsInvalid) {
short BufferWidth = (short)buffer.GetLength(0);
short BufferHeight = (short)buffer.GetLength(1);
CharInfo[] buf = new CharInfo[BufferWidth * BufferHeight];
SmallRect rect = new SmallRect() { Left = 0, Top = 0, Right = BufferWidth, Bottom = BufferHeight };
for (int y = 0; y < BufferHeight; y++) {
for (int x = 0; x < BufferWidth; x++) {
buf[y * BufferWidth + x] = buffer[x, y];
}
}
return WriteConsoleOutput(h, buf, new Coord() { X = BufferWidth, Y = BufferHeight }, new Coord() { X = 0, Y = 0 }, ref rect);
}
return false;
}
}
Character attributes:
[Flags]
public enum CharacterAttribute : ushort {
FOREGROUND_BLUE = 0x0001,
FOREGROUND_GREEN = 0x0002,
FOREGROUND_RED = 0x0004,
FOREGROUND_INTENSITY = 0x0008,
BACKGROUND_BLUE = 0x0010,
BACKGROUND_GREEN = 0x0020,
BACKGROUND_RED = 0x0040,
BACKGROUND_INTENSITY = 0x0080,
COMMON_LVB_LEADING_BYTE = 0x0100,
COMMON_LVB_TRAILING_BYTE = 0x0200,
COMMON_LVB_GRID_HORIZONTAL = 0x0400,
COMMON_LVB_GRID_LVERTICAL = 0x0800,
COMMON_LVB_GRID_RVERTICAL = 0x1000,
COMMON_LVB_REVERSE_VIDEO = 0x4000,
COMMON_LVB_UNDERSCORE = 0x8000
}
Test code:
class Program {
static void Main(string[] args) {
// write to location 0,0
LowLevelConsole.Write("Some test text", CharacterAttribute.BACKGROUND_BLUE | CharacterAttribute.FOREGROUND_RED, 0, 0);
// write to location 5,10
LowLevelConsole.Write("another test at a different location",
CharacterAttribute.FOREGROUND_GREEN | CharacterAttribute.FOREGROUND_BLUE,
5, 10);
Console.ReadLine();
}
}