I previosuly wrote this article in my blog, Think Big!.
Contents
-
Contents
-
Overview
-
Introduction
-
GetStdHandle() Function
-
Marshaling at a Glance
-
GetConsoleScreenBufferInfo() Function
-
Memory Management at a Glance
-
Inside the Memory
-
FillConsoleOutputCharacter() Function
-
SetConsoleCursorPosition() Function
-
Putting Things Together
-
Clearing the Console Screen
-
Inside the .NET Library
-
References
-
Code Sample
Overview
In addition to clearing the console screen, this lesson teaches you about PInvoke, 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 the System.Console.Clear() method does it. More than that you will learn how to reverse-engineer a .NET assembly and discover the code inside.
Also, the example shows how to perform I/O operations on the console using Win32 API calls, and how to show/hide the console cursor. Also it teaches you how to move the text around the console screen.
Introduction
I am one of the huge supporters of the console when it comes to the fact that we need simple and fast applications such as tools and utilities.
One of the common needs of console applications 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 with version 2.0 of the .NET Framework, System.Console has 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 using the Win32 API is done using four Win32 API functions, GetStdHandle(), GetConsoleScreenBufferInfo(), FillConsoleOutputCharacter() and SetConsoleCursorPosition(). It is worth noting that all these functions are located in the Kernel32.dll library.
Be sure that the System.Console.Clear() method in .NET 2.0 and descendants call these four functions -and more- initially.
GetStdHandle() Function
This method is the gate for dealing with the console via the API. Almost every console operation requires a call to GetStdHandle() first.
GetStdHandle() simply returns the handle for the standard input, output and error devices ("stream" in .NET methodology.) This function takes a single argument that specifies which standard device (stream) we wish to retrieve the handle for.
The syntax of this function (in C) is as follows:
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 it's names implies, the input device is used for receiving user input, thee output device is used for writing output to the user, and the error device is used 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 using PInvoke.
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 follows; this code assumes that you add a using statement for the System.Runtime.InteropServices namespace:
[DllImport("Kernel32.dll")]
public static extern IntPtr GetStdHandle(int nStdHandle);
Code explanation:
The DllImport attribute is required for PInvoke methods so that you can specify the DLL that the function resides in. Also DllImport has 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 in the inability to write to the console any more, unless you open it again.
Marshaling at a Glance
Because there're plenty of Win32 data types that do not have corresponding types 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 is what GetStdHandle() actually does.
In addition, you may notice that we have marshaled HANDLE as System.IntPtr, because IntPtr is the best type fit to a 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 is 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 follows:
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. Besides the GetConsoleScreenBufferInfo(), there are 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 foreground color and background 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 for them (.NET does not have data types for the 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;
}
Code explanation:
Because .NET does not have data types for the three structures, and we do not have any substitutes, we have to marshal them. We create them manually and mapped unmanaged to the managed data types. And since the memory layout of these objects are very important, we added the StructLayout attribute and specified LayoutKind.Sequential that informs the marshaller that this structure exists in memory sequentially so the first field comes before the second and so on. Also you might notice that the WORD unmanaged data type is an unsigned 32-bit integer so we marshaled it as UInt32. Also SHORT is a signed 16-bit integer so it is very easy to marshal it as Int16. Refer to the references section for more information about Win32 data types.
BOOL is 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, it is helpful to decorate 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.
Memory Management at a Glance
You already know that every object reserves space in memory. And the memory itself is divided into two parts, Heap and Stack. Objects (directly or indirectly) that inherit from System.ValueType are stack-based (like structures, enumerations and primitive data types.) Conversely, most objects that (directly or indirectly) inherit from System.Object are heap-based (like most objects). And you know also that heap objects are managed by the Garbage Collector (GC) and they may remain in the memory for a long time (actually even if you call System.GC many times). On the other hand, stack objects are removed from memory immediately when their scope ends. In addition, you need to know that the 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 precedes 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 have learned that:
-
Objects are stored downwards in the stack in the order they were created.
-
Objects containing objects are also stored downwards in the order they were declared.
-
Every object has a size. And this size is 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 it's location from the beginning of the structure. In the following code the fields are reversed but they are 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. Also if you have omitted the whole StructLayoutAttribute.
Also, from the diagrams illustrated (especially the last one) we can also write our CONSOLE_SCREEN_BUFFER_INFO structure 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 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 (or more) fields 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.
Inside the Memory
Now, this time we are going to do something interesting. We are going to look at our structure in 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 is stored in 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);
// You can add a break point on the last line,
// or you can use this function to break the code.
System.Diagnostics.Debugger.Break();
}
This code assumes that you have 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, while debugging and after breaking into 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.
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 at how the structure (and its contained structures) are 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 follows:
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 that specifies whether the function succeeded or failed. If the return value is non-zero (true) then the function succeeded, otherwise it 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 parameter that specifies the number of characters written.
Here's the PInvoke method of this function:
We have omitted the COORD structure because we 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 LPDWORD 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 follows:
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 that 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()
{
// Getting the console output device handle
IntPtr handle = GetStdHandle(STD_OUTPUT_HANDLE);
// Getting console screen buffer info
CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo(handle, out info);
// Discovering console screen buffer info
Console.WriteLine("Console Buffer Info:");
Console.WriteLine("--------------------");
Console.WriteLine("Cursor Position:");
Console.WriteLine("t{0}, {1}", info.dwCursorPosition.X, info.dwCursorPosition.Y);
// Is this information right?
Console.WriteLine("Maximum Window Size:");
Console.WriteLine("t{0}, {1}", info.dwMaximumWindowSize.X, info.dwMaximumWindowSize.Y);
// Is this information right?
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("--------------------");
// Location of which to begin clearing
COORD location = new COORD();
location.X = 0;
location.Y = 0;
// What about clearing starting from
// the second line
// location.Y = 1;
// The number of written characters
uint numChars;
FillConsoleOutputCharacter(handle, WHITE_SPACE,
(uint)(info.dwSize.X * info.dwSize.Y),
location, out numChars);
// The new cursor location
COORD cursorLocation = new COORD();
cursorLocation.X = 0;
cursorLocation.Y = 0;
SetConsoleCursorPosition(handle, cursorLocation);
}
Also we can go a step further and write code that clears a specific portion of the console screen buffer; try this code:
static void Main()
{
// Require the user to enter his password
AuthenticateUser();
}
public static void AuthenticateUser()
{
Console.WriteLine("Please enter your password:");
Console.Write("> "); // Two characters right
string input = Console.ReadLine();
while (input != "MyPassword")
{
COORD location = new COORD();
// The third character
location.X = 2;
// The second line
location.Y = 1;
ClearConsoleScreen(location);
input = Console.ReadLine();
}
// User authenticated
Console.WriteLine("Authenticated!");
}
public static void ClearConsoleScreen(COORD location)
{
// Getting the console output device handle
IntPtr handle = GetStdHandle(STD_OUTPUT_HANDLE);
// Getting console screen buffer info
CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo(handle, out info);
// The number of written characters
uint numChars;
FillConsoleOutputCharacter(handle, WHITE_SPACE,
(uint)(info.dwSize.X * info.dwSize.Y),
location, out numChars);
SetConsoleCursorPosition(handle, location);
}
Inside the .NET Library
Here we will look inside the library mscorlib.dll and see how it implements the 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 from here; 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.
Disassembly Using 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 is called the IL Disassembler (ILDasm). You can find this tool in the .NET install directory in %ProgramFiles%Microsoft SDKsWindowsBin for the version 3.5, and Common7bin for versions before.
After opening ILDasm.exe you can open the file mscorlib.dll inside. Click File -> Open and select the library file that is located in %WinDir%\Microsoft.NET\Framework. 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 at how the Clear() method is done.
Finding Disassembly Tools
If MSIL seems odd, you may try another perfect tool 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
Code Sample
Code sample is attached with the article.