Overview
In addition to clearing the console screen, this lesson teaches you some about PInvoking, marshaling, and memory management. Also you will learn additional techniques like clearing a specific portion of the screen, and changing the cursor position. Moreover, you will dig into IL and see how System.Console.Clear()
method do it. More than that you will learn how to reverse-engineer a .NET assembly and discover the code inside.
In addition, the sample application shows how to perform I/O operations on console using Win32 API calls, and how to show/hide the console cursor. Moreover, it teaches you how to move the text around the console screen.
Table of Contents
Table of contents of this article:
- Overview
- Table of Contents
- Introduction
- GetStdHandle() Function
- A Note about Marshaling
- GetConsoleScreenBufferInfo() Function
- A Note about Memory Management
- A Look Inside the Memory
- FillConsoleOutputCharacter() Function
- SetConsoleCursorPosition() Function
- Putting Things Together
- Clearing the Console Screen
- A Look Inside the .NET Library
- References
- Code Sample (The Tiny Console Library)
- Summary
Introduction
I am one of the huge supporters of console when it comes to the fact that we need simple and fast application such as tools and utilities.
One of the common needs of console is performing clear-screens (CLSs) when necessary.
When dealing with the Command Prompt you can order it to clear the screen by submitting the command "cls" and pressing Enter.
But, programmatically, you have no way to order the command "cls," so you must find a substitute (or more.)
Starting from version 2.0 of the .NET Framework,
System.Console
has been added a new method, it is the
Clear()
method for clearing the console screen.
For versions before 2.0, or if you need to do it the hard-way, or even if you want to have more control over the clearing progress such as clearing a specific portion of the screen, you must dig into Win32 API and call special functions for doing so.
Clearing the console screen done through four Win32 API functions,
GetStdHandle()
,
GetConsoleScreenBufferInfo()
,
FillConsoleOutputCharacter()
and
SetConsoleCursorPosition()
. It is worth noting that all these functions located in
Kernel32.dll library.
Be sure that the System.Console.Clear()
method in .NET 2.0 and descendant call these four functions -and more- initially.
GetStdHandle() Function
This method is the gate for dealing with console via the API. Almost every console operation requires a call to
GetStdHandle()
first.
GetStdHandle()
simply returns the handle for the standard input, output, error device ("stream" in .NET methodology.) This function takes a single argument specifies which standard device (stream) we wish to retrieve the handle for.
The syntax of this function -in C- is as following:
HANDLE GetStdHandle(
DWORD nStdHandle
);
The nStdHandle argument can take one of three values:
- STD_INPUT_HANDLE = -10
Specifies the standard input device (stream.)
- STD_OUTPUT_HANDLE = -11
Specifies the standard output device (stream.)
- STD_ERROR_HANDLE = -12
Specifies the standard error device (stream.) It is always the output device (stream.)
As its names implies, input device used for receiving user input, output device used for writing output to the user, and error device used too for writing error information to the user. In .NET, you access these devices (streams) via nice wrappers. They are the static properties In, Out, and Error of the Console class. I guess that after playing with these nice wrappers you now know what standard devices are.
Because there's no such way for .NET to interoperate with Win32 API directly, you need to create wrappers for the Win32 API functions you use, and this is done through PInvoking.
PInvoke stands for Platform Invocations. It is the mechanism for the .NET to interoperate with its "girlfriend" Win32 API.
The PInvoke method for
GetStdHandle()
is as following - code assumes that you add a
using statement for the
System.Runtime.InteropServices
namespace:
[DllImport("Kernel32.dll")]
public static extern IntPtr GetStdHandle(int nStdHandle);
The
DllImport
attribute is required for PInvoke methods so that you can specify the DLL which the function resides in. Also
DllImport
have a very important role when you need to change the name of the method. If you need to change the name of the PInvoke method then you must set the property
EntryPoint
of
DllImportAttribute
to the original name of the function in the DLL. If you have not set this property, .NET will search the DLL for the method and will throw a
System.EntryPointNotFoundException
exception if it is not found.
"
static
" and "
extern
" are modifiers required for PInvoke methods.
Because some data types do not exist in .NET, you need to find a substitute. In other words, you need to marshal unmanaged Win32 data types to the new managed .NET types. So we have marshaled
HANDLE
to
System.IntPtr
and
DWORD
to
System.Int32
.
Only a single output handle serves the console application (the same for input and error devices,) so do not close that opened handle using the CloseHandle()
function because it will result that you cannot write to the console any more, unless you open it again.
A Note about Marshaling
Because there are plenty of Win32 data types that do not have correspondents in .NET, you must do
Marshaling. Marshaling is the process of creating a bridge between .NET types and unmanaged types. Marshaling converts .NET types into unmanaged types. As we have seen in the last code, .NET does not contain
DWORD
, and because
DWORD
is a 32-bit unsigned integer we have to marshal it as
System.UInt32
. However, we marshaled it in
GetStdHandle()
as
System.Int32
! While unsigned integers (inculding
DWORD
) do not accept negative numbers,
GetStdHandle()
requires
DWORD
. Actually, it is OK because negative values can be stored in
DWORD
in a special way. For example, -10 stored as FFFFFFF6, -11 stored as FFFFFFF5, and so on. And that what
GetStdHandle()
actually does.
In addition, you may notice that we have marshaled
HANDLE
as
System.IntPtr
, because
IntPtr
is the best type fits to any Win32 handle. Moreover, .NET supports managed handles via the abstract
SafeHandle
and
CriticalHandle
classes.
It is worth mentioning that
System.Runtime.InteropServices.MarshalAsAttribute
attribute can have a noticeable effect over the marshaling process.
Good resources describe the marshaling process and the Win32 data types can be found in the references section.
Managed code is the .NET code that runs inside the CLR (Common Language Runtime,) because CLR manages and controls this code. Conversely, unmanaged code is the code other than the managed code. It is legal that you write code in .NET that runs outside the CLR such as direct memory management and that code called Unsafe Code because it is error-prone and may result to an unpredictable behavior. Also, MC++ allows you to write unmanaged code together with the managed code.
GetConsoleScreenBufferInfo() Function
The third method we will use is the
GetConsoleScreenBufferInfo()
. This method retrieves information about a specific console screen buffer (in other words, device.) This information is like console screen buffer size, the location and bounds of the console window, and the cursor position inside the screen.
The definition of this function is as following:
BOOL GetConsoleScreenBufferInfo(
HANDLE hConsoleOutput,
[out] SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo
);
struct CONSOLE_SCREEN_BUFFER_INFO {
COORD dwSize;
COORD dwCursorPosition;
WORD wAttributes;
SMALL_RECT srWindow;
COORD dwMaximumWindowSize;
}
struct COORD {
SHORT X;
SHORT Y;
}
struct SMALL_RECT {
SHORT Left;
SHORT Top;
SHORT Right;
SHORT Bottom;
}
Lengthy, isn't it? Yeah, that's true. Beside the
GetConsoleScreenBufferInfo()
, there're three more structures, because the type of second argument of the function is the first structure, and the first structure in turn references the second and third structures.
The
CONSOLE_SCREEN_BUFFER_INFO
structure represents the console information. This information is:
- The size of the console screen buffer in character rows and columns.
- The position of the cursor in the console screen in character rows and columns.
- Characteristics of the character written to the console like the fore color and back color.
- The location and bounds of the console window.
- The maximum size for the console window if we take into account the font size and the screen buffer size.
What is screen buffer and window size and what is the difference? Start the Command Prompt and right click its icon in the taskbar and go to the layout tab of the properties dialog box. Take some time playing with the values in this tab.
Window size is the size of the console window -like any other window.- Screen buffer size determines the amount of text which the console screen can hold, so you always see the vertical scroll bar because the buffer height is bigger than the window height.
Here's the PInvoke method and the marshaling types because .NET does not have such those three structures:
[DllImport("Kernel32.dll")]
public static extern int GetConsoleScreenBufferInfo
(IntPtr hConsoleOutput,
out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo);
[StructLayout(LayoutKind.Sequential)]
public struct CONSOLE_SCREEN_BUFFER_INFO
{
public COORD dwSize;
public COORD dwCursorPosition;
public ushort wAttributes;
public SMALL_RECT srWindow;
public COORD dwMaximumWindowSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
public short X;
public short Y;
}
[StructLayout(LayoutKind.Sequential)]
public struct SMALL_RECT
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
Because .NET does not contain such these three structures, and we do not have any substitutes, so we have to marshal it, we have created it manually and mapped unmanaged to the managed data types. And because memory layout of such these object is very important, we added the
StructLayout
attribute and specified
LayoutKind.Sequential
that informs the marshaller that this structure will laid-out in memory sequentially so the first field comes before the second and so on. Also you might notice that
WORD
unmanaged data type is an unsigned 32-bit integer so we marshaled it as
System.UInt32
. Also
SHORT
is signed 16-bit integer so it is very easy to marshal it as
System.
Int16
. Refer to the references section for more information about Win32 data types.
BOOL
defined as a 32-bit signed integer, so we have marshaled the return value of the function as Int32 not Boolean
because Boolean
reserves 4-bits only. It will work well with Boolean
, but it is better using Int32
. Though, if you want to use Boolean, is helpful decorating the return value with the MarshalAs
attribute.
Also, it is very efficient to use the System.Runtime.InteropServices.InAttribute
and System.Runtime.InteropServices.OutAttribute
to give a notation to the marshaller. The code sample demonstrates this.
A Note about Memory Management
You already know that every object reserves space in memory. And the memory itself divided into two parts, Heap and Stack. Objects -directly or indirectly- inherit from System.ValueType are stack-based (like structures, enumerations and primitive data types.) Conversely, most objects -directly or indirectly- inherit from System.Object
are heap-based (like most objects.) And you know also that heap objects managed by the Garbage Collector (GC) and they may remain in the memory for a long time -actually even if you called System.GC
many times.- On the other hand, stack objects are removed from the memory immediately when their scope ends. In addition, you need to know that stack is filled downwards. See figure 1.
When interoperating with unmanaged code and marshaling types between the .NET and Win32 API (for instance) you need to know how to layout your type in memory. Unmanaged code assumes that types are laid sequentially based on the order of fields. For our example, the CONSOLE_SCREEN_BUFFER_INFO
must be laid-out so dwSize
comes before dwCursorPosition
and so on. See figure 2.
We can dig into more details and see how Windows will store the CONSOLE_SCREEN_BUFFER_INFO
structure in the stack and how it will be rendered. See figure 3.
From the diagrams we learn that:
- Objects are stored downwards in the stack by the order they were created.
- Object containing objects also stored downwards by the order they were declared.
- Every object has a size. And this size determined by its containing objects -if it is not a primitive type of course.-
You can get the size of an object using two ways, the sizeof
keyword, and System.Runtime.InteropServices.Marshal.SizeOf()
method. The second method is preferred. Do you know why? Try, think, and then answer.
Now, we know why the
StructLayout
attribute is required. And why sequential layout is required. But what if you prefer to change the order of fields? Answer is very simple. Change the
LayoutKind
to
Explicit
and decorate every field with
FieldOffset
attribute and specify its location from the beginning of the structure. In the following code fields are reversed but they perfectly laid-out in memory using the
FieldOffset
attribute:
[StructLayout(LayoutKind.Explicit)]
public struct CONSOLE_SCREEN_BUFFER_INFO
{
[FieldOffset(18)]
public COORD dwMaximumWindowSize;
[FieldOffset(10)]
public SMALL_RECT srWindow;
[FieldOffset(8)]
public ushort wAttributes;
[FieldOffset(4)]
public COORD dwCursorPosition;
[FieldOffset(0)]
public COORD dwSize;
}
If you set the LayoutKind
to Auto
, you will not be able to interoperate with unmanaged code using the type. Although, if you have omitted the whole StructLayoutAttribute
.
Also, from the diagrams illustrated -specially the last one- we can also write our
CONSOLE_SCREEN_BUFFER_INFO
structure to be as following and remove the other two structures:
[StructLayout(LayoutKind.Sequential)]
public struct CONSOLE_SCREEN_BUFFER_INFO
{
public short dwSizeX;
public short dwSizeY;
public short dwCursorPositionX;
public short dwCursorPositionY;
public ushort wAttributes;
public short srWindowLeft;
public short srWindowTop;
public short srWindowRight;
public short srWindowBottom;
public short dwMaximumWindowSizeX;
public short dwMaximumWindowSizeY;
}
Sounds odd, isn't it? You can also set the LayoutKind
to Explicit
and start working.
It is also possible to union two fields -or more- into one. For example, merging the two 16-bit integers
dwSizeX
and
dwSizeY
into one 32-bit integer,
dwSize
. It will work very well! In addition, you can divide them in your code using
System.Collections.Specialized.BitVector32
structure.
A Look Inside the Memory
Now, this time we are going to do something interesting. We are going to look on our structure in the memory.
For simplicity, we are going to use these types:
[StructLayout(LayoutKind.Sequential)]
public struct CONSOLE_SCREEN_BUFFER_INFO
{
public COORD dwSize;
public COORD dwCursorPosition;
public ushort wAttributes;
public SMALL_RECT srWindow;
public COORD dwMaximumWindowSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
public short X;
public short Y;
}
[StructLayout(LayoutKind.Sequential)]
public struct SMALL_RECT
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
Next, we will initialize the structures with some data to see how it stored in the memory.
unsafe static void Main()
{
CONSOLE_SCREEN_BUFFER_INFO info
= new CONSOLE_SCREEN_BUFFER_INFO();
info.dwSize = new COORD();
info.dwSize.X = 0xA1;
info.dwSize.Y = 0xA2;
info.dwCursorPosition = new COORD();
info.dwCursorPosition.X = 0xB1;
info.dwCursorPosition.Y = 0xB2;
info.wAttributes = 0xFFFF;
info.srWindow = new SMALL_RECT();
info.srWindow.Left = 0xC1;
info.srWindow.Top = 0xC2;
info.srWindow.Right = 0xC3;
info.srWindow.Bottom = 0xC4;
info.dwMaximumWindowSize = new COORD();
info.dwMaximumWindowSize.X = 0xD1;
info.dwMaximumWindowSize.Y = 0xD2;
uint memoryAddress =
(uint)&info;
Console.WriteLine(
"Memory Address: 0x{0:X}",
memoryAddress);
Console.WriteLine("Press any key . . .");
Console.ReadKey(true);
System.Diagnostics.Debugger.Break();
}
This code assumes that you enabled the unsafe code from the Build tab of the project properties. Also, it assumes that you run your code in the debugging mode by pressing F5, or by clicking Start Debugging from the Debug menu.
Now, and after breaking the code, click Debug -> Windows -> Memory -> Memory 1 to open a memory window. Figure 4 shows how to open a memory window. And figure 5 shows the memory window opened. Click on the figures to enlarge.
Now, you can locate your structure in memory by typing its memory address. Figure 6 shows the structure in the memory window.
Now, take your time looking on how the structure -and its contained structures- laid-out in memory.
FillConsoleOutputCharacter() Function
Last but not least, this is the fourth function we will use. This function fills a specific console buffer portion with a specific character. Passing a white space as the character means clearing this portion.
The syntax of this function is as following:
BOOL FillConsoleOutputCharacter(
HANDLE hConsoleOutput,
TCHAR cCharacter,
DWORD nLength,
COORD dwWriteCoord,
[out] LPDWORD lpNumberOfCharsWritten
);
This function takes four input arguments and a single output one, and it returns a value determines whether the function succeeded or failed. If the return value is non-zero (true) then the function succeeded, otherwise failed (that's for
GetConsoleBufferInfo()
and
SetConsoleCursorPosition()
also.)
The five arguments are:
- hConsoleOutput:
A handle to an opened console output device to write to.
- cCharacter:
The character which to fill the buffer portion with.
- nLength:
The number of characters to write.
- dwWriteCoord:
A COORD structure defines where to begin writing (the first cell.)
- lpNumberOfCharsWritten:An output parameters determines the number of characters written.
Here's the PInvoke method of this function:
We have omitted the COORD
structure because we have created it earlier.
[DllImport("Kernel32.dll")]
public static extern int FillConsoleOutputCharacter
(IntPtr hConsoleOutput, char cCharacter, uint nLength,
COORD dwWriteCoord, out uint lpNumberOfCharsWritten);
Notice that the unmanaged data types DWORD and LPDOWRD
are marshaled to System.UInt32
. For more information about unmanaged data types see the references section.
SetConsoleCursorPosition() Function
This function is used to set the cursor position in the console screen buffer.
The syntax of this function is as following:
BOOL SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD dwCursorPosition
);
This function takes two arguments the first is a handle for an opened console output device. The second is a value specifies the new cursor position. Note that the new cursor position must be inside the console screen buffer.
The PInvoke method for this function is:
[DllImport("Kernel32.dll")]
public static extern int SetConsoleCursorPosition
(IntPtr hConsoleOutput, COORD dwCursorPosition);
Putting Things Together
After all, we have learned the required functions and created the PInvoke method, so we can mix it up and start coding. Here's the full code:
public const int STD_OUTPUT_HANDLE = -11;
public const char WHITE_SPACE = ' ';
[DllImport("Kernel32.dll")]
public static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("Kernel32.dll")]
public static extern int GetConsoleScreenBufferInfo
(IntPtr hConsoleOutput,
out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo);
[DllImport("Kernel32.dll")]
public static extern int FillConsoleOutputCharacter
(IntPtr hConsoleOutput, char cCharacter, uint nLength,
COORD dwWriteCoord, out uint lpNumberOfCharsWritten);
[DllImport("Kernel32.dll")]
public static extern int SetConsoleCursorPosition
(IntPtr hConsoleOutput, COORD dwCursorPosition);
[StructLayout(LayoutKind.Sequential)]
public struct CONSOLE_SCREEN_BUFFER_INFO
{
public COORD dwSize;
public COORD dwCursorPosition;
public ushort wAttributes;
public SMALL_RECT srWindow;
public COORD dwMaximumWindowSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
public short X;
public short Y;
}
[StructLayout(LayoutKind.Sequential)]
public struct SMALL_RECT
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
Clearing the Console Screen
And this is the code that clears the console screen:
static void Main()
{
Console.WriteLine("Writing some text to clear.");
Console.WriteLine("Press any key to clear . . . ");
Console.ReadKey(true);
ClearConsoleScreen();
}
public static void ClearConsoleScreen()
{
IntPtr handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo(handle, out info);
Console.WriteLine("Console Buffer Info:");
Console.WriteLine("--------------------");
Console.WriteLine("Cursor Position:");
Console.WriteLine("t{0}, {1}",
info.dwCursorPosition.X, info.dwCursorPosition.Y);
Console.WriteLine("Maximum Window Size:");
Console.WriteLine("t{0}, {1}",
info.dwMaximumWindowSize.X,
info.dwMaximumWindowSize.Y);
Console.WriteLine("Screen Buffer Size:");
Console.WriteLine("t{0}, {1}",
info.dwSize.X, info.dwSize.Y);
Console.WriteLine("Screen Buffer Bounds:");
Console.WriteLine("t{0}, {1}, {2}, {3}",
info.srWindow.Left, info.srWindow.Top,
info.srWindow.Right, info.srWindow.Bottom);
Console.WriteLine("--------------------");
COORD location = new COORD();
location.X = 0;
location.Y = 0;
uint numChars;
FillConsoleOutputCharacter(handle, WHITE_SPACE,
(uint)(info.dwSize.X * info.dwSize.Y),
location, out numChars);
COORD cursorLocation = new COORD();
cursorLocation.X = 0;
cursorLocation.Y = 0;
SetConsoleCursorPosition(handle, cursorLocation);
}
Also we can step further and write code that clears a specific portion of the console screen buffer, try this code:
static void Main()
{
AuthenticateUser();
}
public static void AuthenticateUser()
{
Console.WriteLine("Please enter your password:");
Console.Write("> ");
string input = Console.ReadLine();
while (input != "MyPassword")
{
COORD location = new COORD();
location.X = 2;
location.Y = 1;
ClearConsoleScreen(location);
input = Console.ReadLine();
}
Console.WriteLine("Authenticated!");
}
public static void ClearConsoleScreen
(COORD location)
{
IntPtr handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo(handle, out info);
uint numChars;
FillConsoleOutputCharacter(handle, WHITE_SPACE,
(uint)(info.dwSize.X * info.dwSize.Y),
location, out numChars);
SetConsoleCursorPosition(handle, location);
}
A Look Inside the .NET Library
Here we will look inside the library mscorlib.dll and see how it implements
System.Console.Clear()
method. This library is the core library that every .NET application must reference, it defines the core classes and components that are essential for every application. Also, it contains classes that are required by the
CLR (Common Language Runtime.)
If you are using .NET 2.0 or higher you can continue this section, otherwise, you may pass this section because the Clear()
method is new with the .NET 2.0.
MSIL, CIL, and IL all refer to the same thing the intermediate Language.
MSCORLIB stands for Microsoft Common Object Runtime Library.
Using the IL Disassembler
.NET Framework comes with a tool that allows you to reverse-engineer any assembly and view its MSIL (Microsoft Intermediate Language) instructions. This tool called IL Disassembler (ILDasm). You can find this tool in the .NET install directory in %ProgramFiles%\Microsoft SDKs\Windows<WindowsVersion>\Bin for the version 3.5, and <VSInstallDir>\Common7\bin for versions before.
After opening ILDasm.exe you can open the file mscorlib.dll inside. Click File -> Open and select the library file which located in %WinDir%\Microsoft.NET\Framework\<RuntimeVersion>. Figure 7 shows IL Disassembler with the mscorlib.dll opened.
Now open the namespace System, then step down to the System.Console
class and double-click the Clear()
method to show the IL instructions. Take a look on how the Clear()
method do it.
Using Another Tool
If MSIL seems odd, you may try another perfect tools that can reverse-engineer your assembly to your preferred language (like C# or VB.NET for instance.)
Some famous tools are Lutz Roeder's .NET Reflector and XenoCode Fox.
For me, I prefer the first tool because it is faster, simpler, and supports many features.
Of course you can reflect (reverse-engineer) an assembly and learn nice tricks from the code inside.
References
Sample Code (The Tiny Console Library)
This sample code demonstrates some of the hidden functionality of Console applications, such as moving a text around the screen and clearing a specific portion of the screen.
Download the Sample Code
Summary
So you have learned how to clear the console screen using Win32 API. In addition, you learned many techniques including PInvoking, marshaling, memory management, and how to reverse-engineer a .NET assembly and get its code out. Moreover, you learned many ideas to apply when working with unmanaged code via .NET. Be sure to check the sample application - it provides you with many features that you will not find in the .NET Framework SDK (nor many other SDKs I think.) It illustrates many techniques including how to clear a specific portion of the screen and how to move a text around the screen. In addition, it shows all common console operations, like reading and writing to the screen buffer, done through the Win32 API and .NET. D