title | description | author | ms.author | ms.topic | keywords | f1_keywords | MS-HAID | MSHAttr | ms.assetid | topic_type | api_name | api_location | api_type | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
GetStdHandle function |
Retrieves a handle to the specified standard device (standard input, standard output, or standard error). |
miniksa |
miniksa |
article |
console, character mode applications, command line applications, terminal applications, console api |
|
|
|
23cd76e9-671a-48d0-9b82-2beda8917348 |
apiref |
GetStdHandle |
|
DllExport |
GetStdHandle function
Retrieves a handle to the specified standard device (standard input, standard output, or standard error).
Syntax
HANDLE WINAPI GetStdHandle(
_In_ DWORD nStdHandle
);
Parameters
nStdHandle [in]
The standard device. This parameter can be one of the following values.
Value | Meaning |
---|---|
STD_INPUT_HANDLE ((DWORD)-10) |
The standard input device. Initially, this is the console input buffer, CONIN$ . |
STD_OUTPUT_HANDLE ((DWORD)-11) |
The standard output device. Initially, this is the active console screen buffer, CONOUT$ . |
STD_ERROR_HANDLE ((DWORD)-12) |
The standard error device. Initially, this is the active console screen buffer, CONOUT$ . |
[!NOTE]
The values for these constants are unsigned numbers, but are defined in the header files as a cast from a
signed number and take advantage of the C compiler rolling them over to just under the maximum 32-bit value. When interfacing with these handles in a language that does not parse the headers and is re-defining the constants, please be aware of this constraint. As an example,((DWORD)-10)
is actually the unsigned number4294967286
.
Return value
If the function succeeds, the return value is a handle to the specified device, or a redirected handle set by a previous call to SetStdHandle. The handle has GENERIC_READ and GENERIC_WRITE access rights, unless the application has used SetStdHandle to set a standard handle with lesser access.
[!TIP]
It is not required to dispose of this handle with CloseHandle when done. See Remarks for more information.
If the function fails, the return value is INVALID_HANDLE_VALUE. To get extended error information, call GetLastError.
If an application does not have associated standard handles, such as a service running on an interactive desktop, and has not redirected them, the return value is NULL.
Remarks
Handles returned by GetStdHandle can be used by applications that need to read from or write to the console. When a console is created, the standard input handle is a handle to the console’s input buffer, and the standard output and standard error handles are handles of the console’s active screen buffer. These handles can be used by the ReadFile and WriteFile functions, or by any of the console functions that access the console input buffer or a screen buffer (for example, the ReadConsoleInput, WriteConsole, or GetConsoleScreenBufferInfo functions).
The standard handles of a process may be redirected by a call to SetStdHandle, in which case GetStdHandle returns the redirected handle. If the standard handles have been redirected, you can specify the CONIN$
value in a call to the CreateFile function to get a handle to a console’s input buffer. Similarly, you can specify the CONOUT$
value to get a handle to a console’s active screen buffer.
The standard handles of a process on entry of the main method are dictated by the configuration of the /SUBSYSTEM flag passed to the linker when the application was built. Specifying /SUBSYSTEM:CONSOLE requests that the operating system fill the handles with a console session on startup, if the parent didn’t already fill the standard handle table by inheritance. On the contrary, /SUBSYSTEM:WINDOWS implies that the application does not need a console and will likely not be making use of the standard handles. More information on handle inheritance can be found in the documentation for STARTF_USESTDHANDLES.
Some applications operate outside the boundaries of their declared subsystem; for instance, a /SUBSYSTEM:WINDOWS application might check/use standard handles for logging or debugging purposes but operate normally with a graphical user interface. These applications will need to carefully probe the state of standard handles on startup and make use of AttachConsole, AllocConsole, and FreeConsole to add/remove a console if desired.
Some applications may also vary their behavior on the type of inherited handle. Disambiguating the type between console, pipe, file, and others can be performed with GetFileType.
Handle disposal
It is not required to CloseHandle when done with the handle retrieved from GetStdHandle. The returned value is simply a copy of the value stored in the process table. The process itself is generally considered the owner of these handles and their lifetime. Each handle is placed in the table on creation depending on the inheritance and launch specifics of the CreateProcess call and will be freed when the process is destroyed.
Manual manipulation of the lifetime of these handles may be desirable for an application intentionally trying to replace them or block other parts of the process from using them. As a HANDLE
can be cached by running code, that code will not necessarily pick up changes made via SetStdHandle. Closing the handle explicitly via CloseHandle will close it process-wide and the next usage of any cached reference will encounter an error.
Guidance for replacing a standard handle in the process table would be to get the existing HANDLE
from the table with GetStdHandle, use SetStdHandle to place a new HANDLE
in that is opened with CreateFile (or a similar function), then to close the retrieved handle.
There is no validation of the values stored as handles in the process table by either the GetStdHandle or SetStdHandle functions. Validation is performed at the time of the actual read/write operation such as ReadFile or WriteFile.
Attach/detach behavior
When attaching to a new console, standard handles are always replaced with console handles unless STARTF_USESTDHANDLES was specified during process creation.
If the existing value of the standard handle is NULL, or the existing value of the standard handle looks like a console pseudohandle, the handle is replaced with a console handle.
When a parent uses both CREATE_NEW_CONSOLE and STARTF_USESTDHANDLES to create a console process, standard handles will not be replaced unless the existing value of the standard handle is NULL or a console pseudohandle.
[!NOTE]
Console processes must start with the standard handles filled or they will be filled automatically with appropriate handles to a new console. Graphical user interface (GUI) applications can be started without the standard handles and they will not be automatically filled.
Examples
For an example, see Reading Input Buffer Events.
Requirements
Minimum supported client | Windows 2000 Professional [desktop apps only] |
Minimum supported server | Windows 2000 Server [desktop apps only] |
Header | ProcessEnv.h (via Winbase.h, include Windows.h) |
Library | Kernel32.lib |
DLL | Kernel32.dll |
See also
Console Functions
Console Handles
CreateFile
GetConsoleScreenBufferInfo
ReadConsoleInput
ReadFile
SetStdHandle
WriteConsole
WriteFile
Функция GetStdHandle
Функция GetStdHandle
извлекает дескриптор для стандартного
ввода данных, стандартного вывода или
стандартной ошибки устройства.
Синтаксис
HANDLE GetStdHandle(
DWORD nStdHandle
// ввод, вывод или ошибка устройства
);
Параметры
nStdHandle
[in] Стандартное устройство, для которого
дескриптор должен быть возвращен. Этот
параметр может быть одним из следующих
значений.
Именованная константа |
Предназначение |
|
Дескриптор стандартного устройства |
|
Дескриптор устройства стандартного |
|
Дескриптор стандартной ошибки |
Возвращаемые значения
Если функция завершается успешно,
возвращаемое значение — дескриптор
определяемого устройства. Дескриптор имеет
права доступа GENERIC_READ
и GENERIC_WRITE,
если приложение не использовало функцию SetStdHandle,
чтобы установить стандартный дескриптор с
меньшими правами доступа.
Если функция завершается с ошибкой,
возвращаемое значение — флажок INVALID_HANDLE_VALUE.
Чтобы получить расширенные данные об
ошибках, вызовите функцию
GetLastError.
Замечания
Дескрипторы, возвращенные функцией
GetStdHandle, могут
быть использованы прикладными программами,
которым нужно читают из или записывать в
консоль. Когда консоль создана,
дескриптором стандартного ввода является
дескриптор буфера ввода консоли, а
стандартного вывода и обработки
стандартной ошибки является дескриптор
активного экранного буфера консоли. Эти
дескрипторы могут быть использованы
функциями ReadFile
и WriteFile,
или любой из консольных функций, которые
обращаются к консольному буферу ввода или
экранному буферу (например, функциям
ReadConsoleInput,
WriteConsole,
или GetConsoleScreenBufferInfo).
Стандартные дескрипторы
процесса могут быть переназначен вызовом
функции SetStdHandle,
в этом случае функция GetStdHandle
возвращает переназначенный дескриптор.
Если стандартные дескрипторы были
переназначены, Вы можете задать значение CONIN$
при вызове к функции CreateFile,
чтобы получить дескриптор для буфера ввода
консоли. Точно так же Вы можете задать
значение CONOUT$,
чтобы получить дескриптор для активного
экранного буфера консоли.
Код примера
Пример смотри в статье
Чтение событий
буфера вводимых данных.
Смотри также
Обзор консольных приложений,
Функции
консоли, CreateFile,
GetConsoleScreenBufferInfo,
ReadConsoleInput,
ReadFile,
SetStdHandle,
WriteConsole,
WriteFile
Размещение и совместимость GetStdHandle |
|
Windows. NET Server |
Да |
Windows XP |
Да |
Windows 2000 |
Да |
Windows NT |
Да |
Windows Me |
Да |
Windows 98 |
Да |
Windows 95
|
Да |
Используемая библиотека |
Kernel32.lib |
Заголовочный файл |
|
— объявлено в |
Wincon.h |
— включено в |
Windows.h |
Unicode |
Нет |
Замечания по платформе |
Не имеется |
Contents
- 1 Exception handling
- 1.1 Capture and storage of exception objects
- 1.2 Handling of failures in exception handling
- 1.3 Handling of exception specification violations (removed in C++17)
- 2 Exception categories
- 3 Error numbers
- 4 System error
- 5 Assertions
- 6 Stacktrace
- 7 See also
[edit] Exception handling
The header <exception>
provides several classes and functions related to exception handling in C++ programs.
Defined in header |
|
exception |
base class for exceptions thrown by the standard library components (class) [edit] |
Capture and storage of exception objects |
|
uncaught_exceptionuncaught_exceptions (removed in C++20)(C++17) |
checks if exception handling is currently in progress (function) [edit] |
exception_ptr (C++11) |
shared pointer type for handling exception objects (typedef) [edit] |
make_exception_ptr (C++11) |
creates an std::exception_ptr from an exception object (function template) [edit] |
current_exception (C++11) |
captures the current exception in a std::exception_ptr (function) [edit] |
rethrow_exception (C++11) |
throws the exception from an std::exception_ptr (function) [edit] |
nested_exception (C++11) |
a mixin type to capture and store current exceptions (class) [edit] |
throw_with_nested (C++11) |
throws its argument with std::nested_exception mixed in (function template) [edit] |
rethrow_if_nested (C++11) |
throws the exception from a std::nested_exception (function template) [edit] |
Handling of failures in exception handling |
|
Defined in header |
|
terminate |
function called when exception handling fails (function) [edit] |
terminate_handler |
the type of the function called by std::terminate (typedef) [edit] |
get_terminate (C++11) |
obtains the current terminate_handler (function) [edit] |
set_terminate |
changes the function to be called by std::terminate (function) [edit] |
bad_exception |
exception thrown when std::current_exception fails to copy the exception object (class) [edit] |
Handling of exception specification violations (removed in C++17) |
|
unexpected (removed in C++17) |
function called when dynamic exception specification is violated (function) [edit] |
unexpected_handler (removed in C++17) |
the type of the function called by std::unexpected (typedef) [edit] |
get_unexpected (C++11)(removed in C++17) |
obtains the current unexpected_handler (function) [edit] |
set_unexpected (removed in C++17) |
changes the function to be called by std::unexpected (function) [edit] |
[edit] Exception categories
Several convenience classes are predefined in the header <stdexcept>
to report particular error conditions. These classes can be divided into two categories: logic errors and runtime errors. Logic errors are a consequence of faulty logic within the program and may be preventable. Runtime errors are due to events beyond the scope of the program and can not be easily predicted.
Defined in header |
|
logic_error |
exception class to indicate violations of logical preconditions or class invariants (class) [edit] |
invalid_argument |
exception class to report invalid arguments (class) [edit] |
domain_error |
exception class to report domain errors (class) [edit] |
length_error |
exception class to report attempts to exceed maximum allowed size (class) [edit] |
out_of_range |
exception class to report arguments outside of expected range (class) [edit] |
runtime_error |
exception class to indicate conditions only detectable at run time (class) [edit] |
range_error |
exception class to report range errors in internal computations (class) [edit] |
overflow_error |
exception class to report arithmetic overflows (class) [edit] |
underflow_error |
exception class to report arithmetic underflows (class) [edit] |
tx_exception (TM TS) |
exception class to cancel atomic transactions (class template) |
[edit] Error numbers
[edit] System error
The header <system_error>
defines types and functions used to report error conditions originating from the operating system, streams I/O, std::future, or other low-level APIs.
Defined in header |
|
error_category (C++11) |
base class for error categories (class) [edit] |
generic_category (C++11) |
identifies the generic error category (function) [edit] |
system_category (C++11) |
identifies the operating system error category (function) [edit] |
error_condition (C++11) |
holds a portable error code (class) [edit] |
errc (C++11) |
the std::error_condition enumeration listing all standard <cerrno> macro constants (class) [edit] |
error_code (C++11) |
holds a platform-dependent error code (class) [edit] |
system_error (C++11) |
exception class used to report conditions that have an error_code (class) [edit] |
[edit] Assertions
Assertions help to implement checking of preconditions in programs.
aborts the program if the user-specified condition is not true. May be disabled for release builds (function macro) [edit] |
[edit] Stacktrace
[edit] See also
We can perform console I/O with the ReadFile() and WriteFile() functions, but it’s easier to use the specific console I/O functions, ReadConsole() and WriteConsole(). The ReadConsole() and WriteConsole() functions both accept TCHAR, or generic characters. TCHAR characters can be used to describe either ANSI single byte characters or wide double byte Unicode characters; thus, the TCHAR type allows us to be narrow/wide neutral.
The GetStdHandle() function gives us a mechanism for retrieving the standard input, STDIN, the Standard Output, STDOUT, and the standard error handles, STDERR. The GetStdHandle() function takes a single parameter that can be one of three values, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, and STD_ERROR_HANDLE.
#include "stdafx.h"
#include <Windows.h>
int main()
{
HANDLE hStdin, hStdout, hStderr;
hStdin = GetStdHandle(STD_INPUT_HANDLE);
if (hStdin != INVALID_HANDLE_VALUE) {
printf("Handle to standard input acquired.n");
}
//combine the steps
if ((hStdout = GetStdHandle(STD_OUTPUT_HANDLE)) != INVALID_HANDLE_VALUE) {
printf("Handle to standard output acquired.n");
}
if ((hStderr = GetStdHandle(STD_ERROR_HANDLE)) != INVALID_HANDLE_VALUE){
printf("Handle to standard error acquired.n");
}
return 0;
}
The SetConsoleMode() and GetConsoleMode() functions allow us to modify and view the console mode for the input or screen buffer. The console mode describes how characters are processed via flags; five commonly used flags, which are enabled by default, are ENABLE_LINE_INPUT, ENABLE_ECHO_INPUT, ENABLE_PROCESSED_INPUT, ENABLE_PROCESSED_OUTPUT, and ENABLE_WRAP_AT_EOL_OUTPUT. Both the SetConsoleMode() and GetConsoleMode() functions return a Boolean value indicating success or failure.
#include "stdafx.h" #include <Windows.h> void PrintConsoleInputFlags(DWORD dwFlags); void PrintConsoleOutputFlags(DWORD dwFlags); int main() { HANDLE hStdin, hStdout; DWORD dwStdinFlags, dwStdoutFlags; //get handles to stdin and stdout hStdin = GetStdHandle(STD_INPUT_HANDLE); hStdout = GetStdHandle(STD_OUTPUT_HANDLE); if (hStdin == INVALID_HANDLE_VALUE || hStdout == INVALID_HANDLE_VALUE) { printf("Error getting stdin or stdout.n"); exit(EXIT_FAILURE); } //GetConsoleMode returns Boolean value indicating success if (GetConsoleMode(hStdin, &dwStdinFlags)) { printf("Console mode for stdin acquired.n"); } if (GetConsoleMode(hStdout, &dwStdoutFlags)) { printf("Console mode for stdout acquired.n"); } printf("nFor stdin: n"); PrintConsoleInputFlags(dwStdinFlags); printf("nFot stdout: n"); PrintConsoleOutputFlags(dwStdoutFlags); return 0; } void PrintConsoleInputFlags(DWORD dwFlags) { if (dwFlags & ENABLE_ECHO_INPUT) { printf("Echo input enabled.n"); } if (dwFlags & ENABLE_INSERT_MODE) { printf("Insert mode enabled.n"); } if (dwFlags & ENABLE_LINE_INPUT) { printf("Line input enabled.n"); } if (dwFlags & ENABLE_MOUSE_INPUT) { printf("Mouse input enabled.n"); } if (dwFlags & ENABLE_PROCESSED_INPUT) { printf("Processed input enabled.n"); } } void PrintConsoleOutputFlags(DWORD dwFlags) { if (dwFlags & ENABLE_PROCESSED_OUTPUT) { printf("Processed output enabled.n"); } }
The ReadConsole() and WriteConsole() functions both return TRUE if and only if the action succeeds. The ReadConsole() function takes a handle to the input buffer, and then two length parameters indicating lengths in generic characters, not bytes. The WriteConsole() function is essentially the same, except the buffer is a pointer to a constant.
#include "stdafx.h" #include <Windows.h> int main() { const int Buffer_Length = 2056; HANDLE hStdin, hStdout; DWORD dwCharsRead, dwCharsWritten; TCHAR tszBuffer[Buffer_Length]; BOOL bSuccess; //get handles for standard input //and standard output hStdin = GetStdHandle(STD_INPUT_HANDLE); hStdout = GetStdHandle(STD_OUTPUT_HANDLE); if (hStdin == INVALID_HANDLE_VALUE) { exit(EXIT_FAILURE); } else { printf("Handle to stdin acquired.n"); } if (hStdout == INVALID_HANDLE_VALUE) { exit(EXIT_FAILURE); } else { printf("Handle to stdout acquired.n"); } printf("Please enter in some text!n"); bSuccess = ReadConsole(hStdin, tszBuffer, Buffer_Length - 2, &dwCharsRead, NULL); //replace the enter with the null character if (bSuccess) { printf("Successfully read from console.n"); printf("%d chars read.n", dwCharsRead); tszBuffer[dwCharsRead - 2] = ''; } bSuccess = WriteConsole(hStdout, tszBuffer, _tcslen(tszBuffer), &dwCharsWritten, NULL); if (bSuccess) { printf("nSuccessfully wrote to console.n"); printf("%d chars written.n", dwCharsWritten); } //close the handles! CloseHandle(hStdin); CloseHandle(hStdout); return 0; }
Note that a Windows process can only have one console.
Creating and deleting directories involves a pair of thankfully simple functions, CreateDirectory() and RemoveDirectory(). The CreateDirectory() function takes two arguments, the first is the pathname to the directory to create, and the second is a pointer to a SECURITY_ATTRIBUTES structure. For the second argument, we can simply put NULL. The RemoveDirectory() function has only one parameter, the name of the directory to be removed. Both functions return a Boolean value stating the success or failure of the function call.
#include <Windows.h> #include <stdlib.h> #include <stdio.h> int _tmain(int argc, char argv[]){ if((CreateDirectory("exampleDir", NULL))==FALSE){ printf("Error creating directory.n"); exit(EXIT_FAILURE); } else { printf("Directory created.n"); } if((RemoveDirectory("exampleDir"))==FALSE){ printf("Error removing directory.n"); exit(EXIT_FAILURE); } else { printf("Directory deleted.n"); } return 0; }
A process has a working directory. We can both get and set the working directory. The GetCurrentDirectory() function takes two arguments, the size of the buffer in characters, and the buffer to write the full pathname of the directory to. The SetCurrentDirectory() function has one parameter, the name of the directory we wish to set as the current working directory.
#include <Windows.h> #include <stdio.h> #include <stdlib.h> int _tmain(int argc, char argv[]){ TCHAR szBuffer[MAX_PATH + 1]; TCHAR szNewDir[] = _T("testdir"); DWORD dwRValue = 0; CreateDirectory(szNewDir, NULL); dwRValue = GetCurrentDirectory(MAX_PATH, szBuffer); if(dwRValue != 0){ _tprintf(_T("Current directory is %sn"), szBuffer); } else { printf("Error getting current directory.n"); exit(EXIT_FAILURE); } if(!(SetCurrentDirectory(szNewDir))){ printf("Could not change directory.n"); exit(EXIT_FAILURE); } else { printf("Directory changed.n"); } if(!(GetCurrentDirectory(MAX_PATH, szBuffer))){ printf("Could not get working directory.n"); exit(EXIT_FAILURE); } else { _tprintf(_T("Current directory is %sn"), szBuffer); } return 0; }
The ReadConsole() and WriteConsole() functions are used to read and write from the console. Both the ReadConsole() and WriteConsole() functions take five arguments; the first argument is a HANDLE to standard input or standard output. We get a standard handle using the GetStdHandle() function, which takes a single parameter that can be one of three DWORD values, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, and STD_ERROR_HANDLE .
#include <Window.h>
#include <stdlib.h>
#include <stdio.h>
int _tmain(int argc, char argv[]){
//create a handle to standard output
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
TCHAR *szMsg = "Nice boat!";
DWORD dwCount;
BOOLEAN bRValue;
if(hOut==INVALID_HANDLE_VALUE){
printf("Could not open a file handle to standard output.n");
exit(EXIT_FAILURE);
}
WriteConsole(hOut, szMsg, _tcslen(szMsg), &dwCount, NULL);
if(dwCount == _tcslen(szMsg)){
printf("nThe full message has been printed.n");
} else {
printf("nError! The full message has not printed.n");
}
CloseHandle(hOut);
return 0;
}
Note that a process can only have one console at a time.
Like *nix, a Windows process has three standard devices for input, output, and error reporting; to access these standard devices, Windows needs a HANDLE that is acquired with the GetStdHandle() function. The GetStdHandle() function accepts a DWORD argument and returns a HANDLE on success and INVALID_HANDLE_VALUE otherwise.
The parameter can be one of three values, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, and STD_ERROR_HANDLE.
#include <Windows.h> #include <stdio.h> int _tmain(int argc, _TCHAR* argv[]) { printf("%x n", STD_INPUT_HANDLE); printf("%x n", STD_OUTPUT_HANDLE); printf("%x n", STD_ERROR_HANDLE); return 0; }
The GetStdHandle() function does not create a new or duplicate handle on a standard device. No matter how many times we call GetStdHandle(), the function returns the same handle.
#include #include int _tmain(int argc, _TCHAR* argv[]) { char *szString = "For great justice! "; DWORD dwBytesWritten; HANDLE hStdin = GetStdHandle(STD_OUTPUT_HANDLE); if(hStdin == INVALID_HANDLE_VALUE){ printf("Error accessing standard input.n"); } if(WriteFile(hStdin, szString, strlen(szString), &dwBytesWritten, NULL)){ printf("No problems writing to standard input.n"); } return 0; }
The SetStdHandle() function enables us to redirect standard input, standard output, and standard error. The SetStdHandle() function takes two arguments; the first argument is a DWORD value indicating whether we are redirecting standard input, standard output, or standard error, and the second value is a handle to the file we want to redirect to.
#include <Windows.h> #include <stdio.h> int _tmain(int argc, _TCHAR* argv[]) { HANDLE hFile; hFile = CreateFile("textfile.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE){ printf("Error opening file."); return -1; } if(SetStdHandle(STD_OUTPUT_HANDLE, hFile)==FALSE){ printf("Error setting standard handle.n"); return -2; } //will print to the file //not to the console printf("I survived the Kobayashi Maru!n"); printf("Photons be free!n"); return 0; }
Note that if you call GetStdHandle() after redirecting the standard input, output or error you will get the redirected handle! To recover standard output to the console screen, call CreateFile() with CONOUT$, likewise, a call to CreateFile() with CONIN$ will recover a handle to standard input.
Организация низкоуровнего консольного ввода-вывода
Низкий
уровень консольного ввода-вывода по
сравнению с высоким уровнем и обладает
более широкими и гибкими возможностями.
Низкоуровневые функции консольного
ввода-вывода обеспечивают прямой доступ
к входному и экранным буферам консоли,
предоставляя приложению доступ к
событиям мыши и клавиатуры, а также к
информации об изменении размеров окна
консоли. Функции низкоуровневого
ввода-вывода позволяют приложению иметь
доступ по чтению-записи к указанному
числу последовательных символьных
ячеек в экранном буфере или к прямоугольному
блоку символьных ячеек в указанной
позиции экранного буфера.
Поддержка работы с мышью в консоли
Большое
достоинство консольных приложений —
встроенная средствами Windows поддержка
мыши. Она реализуется с помощью функции
ReadConsolelnput. Важно отметить, что эта функция
используется для получения информация
о событиях не только мыши, но и о событиях
клавиатуры, изменении размера окна и
так далее.
B00L
ReadConsoleInput(
HANDLE
hConsolelnput,
PINPUT_RECORD
lpBuffer,
DWORD
nLength,
LPDWORD
lpNumberOfEventsRead);
hConsolelnput
— стандартный
дескриптор
ввода,
полученный
функцией
GetStdHandle;
lpBuffer
— указатель на буфер, в который
записывается информация о событии мыши,
— эта область памяти имеет структуру,
называемую INPUT_ RECORD;
nLength
— размер во входных записях буфера, на
который указывает указатель lpBuffer;
lpNumberOfEventsRead
— определяет переменную, в которую
записывается
действительное число
прочитанных записей входного буфера.
Расширенная поддержка клавиатуры в консоли
Функции
работы с текстом высокого уровня не
дают других возможностей работы с
клавиатурой, кроме как примитивного
ввода текста. При разработке программ
текстового режима часто требуется
информация о состоянии управляющих
клавиш, о факте удержания клавиши, что
может свидетельствовать о желании
пользователя повторить ввод некоторого
символа или просто о желании получить
тривиальный скан-код клавиши. Эти и
другие события клавиатуры доступны
программе посредством функции
ReadConsolelnput. События клавиатуры генерируются
при нажатии любой клавиши. Процесс их
обработки аналогичен обработке событий
мыши. В первую очередь заполняется о
нажатии некоторых управляющих клавиш.
Для всех остальных клавиш просто
фиксируется факт нажатия. При этом
необходимо помнить, что однократному
нажатию клавиши реально соответствуют
два события — нажатие и отпускание
клавиши. В связи с этим программа выводит
два сообщения. На практике этого можно
избежать, анализируя поле bKeyDown: bKeyDown=l,
когда клавиша нажата; bKeyDown=0, когда
клавиша отпущена. Выход из программы —
при выполнении любых действий с мышью.
Описание используемых функций
Wsprintf
Функция форматирует
и хранит ряд символов и значений в
буфере. Любые параметры преобразуются
и копируются в буфер выводимых данных
согласно соответствующей спецификации
формата в форматируемой строке. Функция
добавляет в конец символ завершающего
нуля к символам, которые она пишет, но
в возвращаемом значении она не включает
его в число символов.
int wsprintf(
LPTSTR lpOut,
LPCTSTR lpFmt,
…
);
lpOut
–
указатель
на буфер, который получит форматированный
вывод данных. Максимальный размер буфера
составляет 1024 байта.
lpFmt
– указатель
на строку с завершающим нулем, которая
содержит в себе спецификации управления
форматом. В дополнение к обычным
символам ASCII спецификация
формата для каждого параметра показывается
в этой строке.
… –
указывает
один или несколько дополнительных
параметров. Число и тип характеристик
параметра зависят от соответствующих
спецификаций управления форматом в
параметре lpFmt.
GetStdHandle
Функция
извлекает дескриптор для стандартного
ввода данных, стандартного вывода или
стандартной ошибки устройства.
HANDLE
GetStdHandle(
DWORD nStdHandle
);
nStdHandle
– стандартное
устройство, для которого дескриптор
должен быть возвращен.
На
вход функции GetStdHandle должно быть подано
одно из следующих значений:
-
STD_INPUT_HANDLE
= -10 — дескриптор стандартного входного
потока; -
STD_OUTPUT_HANDLE
= -11 — дескриптор стандартного выходного
потока; -
STD_ERROR_HANDLE
= -12 — дескриптор стандартного потока
ошибок.
Дескрипторы,
возвращенные функцией GetStdHandle, могут
быть использованы прикладными программами,
которым нужно читать из консоли или
записывать в консоль. Когда консоль
создана, дескриптором стандартного
ввода является дескриптор буфера ввода
консоли, а стандартного вывода и обработки
стандартной ошибки является дескриптор
активного экранного буфера консоли.
Эти дескрипторы могут быть использованы
функциями ReadFile и WriteFile,
или любой из консольных функций, которые
обращаются к консольному буферу ввода
или экранному буферу.
Стандартные
дескрипторы процесса могут быть
переназначен вызовом функции SetStdHandle,
в этом случае функция GetStdHandle возвращает
переназначенный дескриптор. Если
стандартные дескрипторы были переназначены,
можно задать значение CONIN$
при вызове к функции CreateFile,
чтобы получить дескриптор для буфера
ввода консоли. Точно так же можно задать
значение CONOUT$,
чтобы получить дескриптор для активного
экранного буфера консоли.
WriteConsole
Функция
записывает символьную строку в экранный
буфер консоли, начинающийся с текущей
позиции курсора.
BOOL
WriteConsole(
HANDLE hConsoleOutput,
CONST VOID * lpBuffer,
DWORD nNumberOfCharsToWrite,
LPDWORD lpNumberOfCharsWritten,
LPVOID lpReserved );
hConsoleOutput
–
дескриптор
экранного буфера консоли.
lpBuffer
–
указатель
на буфер, содержащий символы, которые
будут записаны в экранный буфер консоли.
Общий размер должен быть меньше чем
64КБ.
nNumberOfCharsToWrite
– число TCHARs для
чтения.
lpNumberOfCharsWritten
–
указатель
на переменную, которая принимает число
фактических записей TCHARs.
lpReserved
—
зарезервировано,
должно быть ПУСТО (NULL).
Функция WriteConsole записывает
символы в экранный буфер консоли. Она
ведет себя подобно функции WriteFile и,
кроме того, может записывать или в
режиме ANSI или УНИКОДЕ.
Чтобы создать приложение, которое
поддерживает единственный набор
источников информации, совместимых с
обоими режимами, используют
функцию WriteConsole, а
не WriteFile.
Хотя WriteConsole может
быть использована только с дескриптором
экранного буфера консоли, WriteFile может
быть использована с другими дескрипторами
(такими как файлы или каналы).
Функция WriteConsole завершается
ошибкой, если используется со стандартным
дескриптором, который был переназначен,
чтобы быть несколько другим, чем
консольный дескриптор.
Функция WriteConsole записывает
символы в экранном буфере консоли в
текущей позиции курсора. Позиция курсора
продвигается вперед, по мере написания
символов.
Символы
пишутся, с использованием атрибутов
цвета текста и цвета фона, связанных с
экранным буфером консоли.
SetConsoleCursorPosition
Функция
устанавливает
позицию курсора в заданном экранном
буфере консоли.
BOOL
SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD dwCursorPosition );
hConsoleOutput
–
дескриптор
экранного буфера консоли.
dwCursorPosition
– структура COORD,
которая устанавливает новую позицию
курсора. Координаты — столбец и ряд
символьного знакоместа экранного
буфера. Координаты должны быть в пределах
границ экранного буфера консоли.
Если
новая позиция курсора не в пределах
границ окна экранного буфера консоли,
начало координат окна изменяется, чтобы
сделать курсор видимым.
SetConsoleTitle
Функция
предназначена
для отображения заголовка окна консоли
B00L
SetConsolеTitle(LPCTSTR lpConsoleTitle) ;
Функция
SetConsoleTitle имеет один параметр — указатель
на строку с заголовком консоли,
заканчивающуюся нулем.
FreeConsole
Функция отключает
вызывающий процесс от его консоли.
BOOL
FreeConsole(VOID);
Если
другие процессы совместно используют
консоль, консоль не разрушается, но
вызывающий процесс не может ссылаться
на неё.
Процесс
может использовать функцию FreeConsole,
чтобы отключить себя от своей текущей
консоли, а затем он может
вызывать функцию AllocConsole,
чтобы создать новую консоль
или AttachConsole,
чтобы подключиться на другую консоль.
AllocConsole
Функция
назначает новую консоль для вызывающего
процесса.
BOOL
AllocConsole(void);
Процесс
может быть связан с только одной консолью,
так что функция AllocConsole завершается
ошибкой, если вызывающий процесс уже
имеет консоль. Процесс может
использовать функцию FreeConsole,
чтобы отключить себя от своей текущей
консоли, затем он может
вызывать функцию AllocConsole,
чтобы создать новую консоль
или функцию AttachConsole,
чтобы подключиться к другой консоли.
Если
вызывающий процесс создает дочерний
процесс, ребенок наследует новую консоль.
Функция AllocConsole инициализирует
стандартный ввод данных, стандартный
вывод и обработку стандартной ошибки
для новой консоли. Дескриптор стандартного
ввода – это дескриптор буфера ввода
консоли, а дескрипторы стандартного
вывода, и стандартной ошибки – это
дескрипторы экранного буфера консоли.
Чтобы получить эти дескрипторы,
используйте функцию GetStdHandle.
CharToOemA
Функция преобразует
строку в определенный OEM набор
символов.
BOOL CharToOem(
LPCTSTR lpszSrc,
LPSTR lpszDst
);
lpszSrc
–указатель
на преобразуемую строку с завершающим
нулем.
lpszDst
–
указатель
на буфер для транслируемой строки. Если
функция CharToOem используется
как функция ANSI,
строка может быть преобразована на
месте путем установки параметра lpszDst для
того же самого адреса, что и параметр lpszSrc.
Этого нельзя делать, если CharToOem используется
как широкосимвольная (Unicode)
функция.
SetConsoleTextAttribute
Функция устанавливает
атрибуты символов, записанных в экранный
буфер консоли функцией WriteFile или WriteConsole,
или повторенных в эхо
режиме функцией ReadFile или ReadConsole.
Эта функция воздействует на текст,
записанный после вызова функции.
BOOL
SetConsoleTextAttribute(
HANDLE hConsoleOutput,
WORD wAttributes
);
hConsoleOutput
– дескриптор
экранного буфера консоли.
wAttributes
–
цвет текста
и фона
Чтобы
выяснять текущие атрибуты цвета экранного
буфера, необходимо
вызвать функцию GetConsoleScreenBufferInfo.
ReadConsoleInputA
Функция читает
данные из консольного буфера ввода и
удаляет их из буфера.
BOOL
ReadConsoleInput(
HANDLE hConsoleInput,
PINPUT_RECORD lpBuffer ,
DWORD nLength ,
LPDWORD lpNumberOfEventsRead
);
hConsoleInput
–
дескриптор
консольного буфера ввода.
lpBuffer
– указатель
на массив структур INPUT_RECORD,
который принимает данные буфера ввода.
Общий размер требуемого массива должен
быть меньше чем 64КБ.
nLength
– размер
массива, указанного параметром lpBuffer,
в элементах массива.
lpNumberOfEventsRead
– указатель
на переменную, которая принимает число
прочитанных записей вводимых данных.
Если
число записей, предписываемых
в параметре nLength, превышает
число записей, доступных в буфере,
читается доступное число. Функция не
возвращает значения до тех пор, пока,
по крайней мере, не будет прочитана одна
запись вводимых данных.
Процесс
может установить консольный дескриптор
буфера ввода в одной из функций
ожидания,
чтобы выяснить, когда имеется непрочитанный
консольный ввод данных. Когда буфер
ввода не пуст, состояние консольного
дескриптора буфера ввода является
сигнальным.
Чтобы
выяснять число непрочитанных записей
вводимых данных в буфере ввода консоли,
используют функцию GetNumberOfConsoleInputEvents.
Чтобы
читать записи вводимых данных из
консольного буфера ввода без воздействия
на число непрочитанных записей,
используют функцию PeekConsoleInput.
Чтобы сбросить все непрочитанные записи
в буфере ввода консоли,
используют функцию FlushConsoleInputBuffer.
ExitProcess
Функция заканчивает
работу процесса и всех его потоков.
VOID ExitProcess(
UINT uExitCode
);
uExitCode
– определяет
код выхода для процесса, и для всех
потоков, которые завершают работу в
результате вызова этой функции. Для
того, чтобы получить значение выхода
из процесса необходимо используют
функцию GetExitCodeProcess.
Для того, чтобы получить значение выхода
из потока необходимо использовать
функцию GetExitCodeThread.
Функция ExitProcess —
предпочтительный метод завершения
процесса. Эта функция обеспечивает
чистое отключение процесса. Такое
завершение включает в себя вызов функций
точек входа всех связанных динамически
подключаемых библиотек (DLL)
со значениями, указывающими, что процесс
отключается от DLL.
Если процесс заканчивается путем
вызова TerminateProcess, DLL,
к которым процесс подключен, не
уведомляются о завершении процесса.
После того, как все связанные DLL исполнили
любое значение завершения, эта функция
завершает работу текущего процесса.
Завершение
процесса происходит по нижеследующим
причинам:
1.
Все дескрипторы объектов, открытые
процессом, закрываются.
2.
Все потоки в процессе завершают свою
работу по исполнению кода.
3.
Состояние объекта процесса становится
сигнальным, удовлетворяя любые потоки,
которые ждали завершения процесса.
4.
Состояния всех потоков процесса,
становятся сигнальными, удовлетворяя
любые потоки, которые ждали завершения
работы потоков.
5.
Состояние завершения процесса изменяется
из STILL_ACTIVE в
значение выхода процесса.
Завершение
процесса не заставляет дочерние процессы
закончить свою работу.
Завершение
процесса необязательно удаляет объект
процесса из операционной системы. Объект
процесса удаляется тогда, когда
закрывается последний дескриптор
процесса.
KEY_EVENT
— клавиатурное
событие
Смещение |
Длина |
Значение |
+4 |
4 |
При |
+8 |
2 |
Количество |
+10 |
2 |
Виртуальный |
+12 |
2 |
Скан-код |
+14 |
2 |
Для |
+16 |
4 |
Содержится |
MOUSE_EVENT
– событие
с мышью
Смещение |
Длина |
Значение |
+4 |
4 |
Младшее |
+8 |
4 |
Описывает |
+12 |
4 |
Состояние |
+16 |
4 |
Может |
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 notBoolean
becauseBoolean
reserves 4-bits only. It will work well withBoolean
, but it is better usingInt32
. Though, if you want to use Boolean, is helpful decorating the return value with theMarshalAs
attribute.
Also, it is very efficient to use the
System.Runtime.InteropServices.InAttribute
andSystem.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, andSystem.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
toAuto
, you will not be able to interoperate with unmanaged code using the type. Although, if you have omitted the wholeStructLayoutAttribute
.
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
toExplicit
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 toSystem.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 SDKsWindows<WindowsVersion>Bin for the version 3.5, and <VSInstallDir>Common7bin 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.NETFramework<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
- Win32 Data Types
- GetStdHandle() function
- GetConsoleScreenBufferInfo() function
- FillConsoleOutputCharacter() function
- SetConsoleCursorPosition() function
- CONSOLE_SCREEN_BUFFER_INFO structure
- COORD structure
- SMALL_RECT structure
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
Mohammad Elsheimy is a developer, trainer, and technical writer. He is a MCP, MCTS (WinForms), MCPD (Windows Apps), MCSA (SQL Server), MCSE (Data Analytics), and MCT expertized in .NET Framework technologies, data management and analytics. He is also a Project Management Professional (PMP) and a Quranic Readings Institute (Al-Azhar) graduate specialized in Quranic readings, Islamic legislation, and the Arabic language.