Урок 2.

Что есть консоль.

   Консольные программы  - что это? О, это для тех, кто любит работать с командной строкой. Самая знаменитая консольная программа - это Far. Но дело ведь не только в любви к текстовому режиму. Часто нет необходимости и времени для создания графического интерфейса, а программа должна что-то делать, например, обрабатывать большие объемы информации. И вот тут на помощь приходят консольные приложения. Ниже Вы увидите, что консольные приложения очень компактны не только в откомпилированном виде, но и в текстовом варианте. Но главное, консольное приложение имеет такие же возможности обращаться к ресурсам Windows посредством API-функций, как и обычное оконное графическое приложение.

    И так с чего же начнем. Материала довольно много. Ограничим себе лишь следующими вопросами:

Ввод и вывод. 

    Консольные приложения могут создать свою консоль. В этом случае весь ввод-вывод будет производиться в эту консоль. Если же приложение консоль не создает, то здесь может возникнуть двоякая ситуация: либо наследуется консоль, в которой программа была запущена, либо Windows создает для приложения свою консоль.  И вот пример не заставляет себя ждать.

;консольное приложение console1
.386P
;плоская модель
.MODEL FLAT, stdcall
;константы
STD_OUTPUT_HANDLE equ -11
;прототипы внешних процедур
EXTERN GetStdHandle@4:NEAR
EXTERN WriteConsoleA@20:NEAR
EXTERN ExitProcess@4:NEAR 
;директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
;------------------------------------------------
;сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
    STR1 DB "This a console application!"
    LENS DD ? ;количество выведенных символов
    RES DD 0
_DATA ENDS
;сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
    ;получить HANDLE вывода
    PUSH STD_OUTPUT_HANDLE
    CALL GetStdHandle@4
    ;вывести строку
    PUSH OFFSET RES ;резерв
    PUSH OFFSET LENS ;выведено символов
    PUSH 27 ;длина строки
    PUSH OFFSET STR1 ;адрес строки
    PUSH EAX ;HANDLE вывода
    CALL  WriteConsoleA@20
    PUSH 0
    CALL ExitProcess@4
_TEXT ENDS
END START

Стало быть начнем  разбирать программу подробнее. У начинающего, я думаю, вопросов море. Но попорядку. 

HANDLE GetStdHandle(DWORD nStdHandle) - с помощью данной функции можно получить дескриптор стандартного ввода, вывода или устройства вывода сообщений об ошибке. Единственный аргумент функции и указывает, что мы хотим получить. Мы используем константу STD_OUTPUT_HANDLE, т.е. желаем получить дескриптор вывода.

BOOL WriteConsole(
  HANDLE
hConsoleOutput,           
  CONST VOID *lpBuffer,            
  DWORD nNumberOfCharsToWrite,     
  LPDWORD lpNumberOfCharsWritten,  
  LPVOID lpReserved ) - эта функция выводит на консоль текстовую строку. Первый параметр и есть дескриптор консоли, полученный с помощью предыдущей функции. Второй параметр - указатель на буфер, где содержится выводимый текст. Третий  параметр содержит количество выводимых символов. Четвертый указывает на переменную, куда будет помещено количесвто реально выведенных символов. Наконец четвертый параметр это резерв. В документации Microsoft сказано, что данный указатель должен быть равен NULL, т.е. равным нулю. Мы не следуем этому правилу и без всяких при этом последствий. Наконец, заметим, что в случае успешного выполнения функция возвращает не нулевое значение и нулю, если в процессе вывода произошла ошибка. Мы, в своей программе, не проверяем правильность выполнения функции.  

VOID ExitProcess(UINT uExitCode) - данная функция корректно заканчивает работу приложения. Единственным ее аргментом является код выхода. Мы как видно из программы полагаем этот код равным нулю. 

И так, функции я Вам описал, а теперь снова взгляните на программу и вспомните, что говорилось в предыдущем уроке о порядке помещения параметров в стек («слева  на­право - снизу вверх»), о префиксах "A" и "@N". Не правда ли, все понятно?

        Перейдем теперь, к чтению из консоли. Для начала разберем работу функции чтения.

BOOL ReadConsole(
  HANDLE
hConsoleInput,         
  LPVOID lpBuffer,              
  DWORD nNumberOfCharsToRead,   
  LPDWORD lpNumberOfCharsRead,  
  LPVOID lpReserved) - функция чтения очень похожа на функцию записи. Первым параметром функции будет дескриптор стандартного ввода консоли. Получить этот дескриптор можно также с помощью функции GetStdHandle, использую как параметр значения STD_INPUT_HANDLE. Второй параметр - это указатель на буфер, куда будет помещен считываемый текст. Третий параметр функции содержит количество считываемых байт (длина буфера). Четвертый параметр есть указатель на двойное слово, куда будет помещено количество фактически считанных байт - это важно, т.к. прекратить ввод строки можно нажав клавишу Enter. Наконец последний параметр зарезервирован для будущих использований. 

Ну, что же, а теперь простой пример. Он похож на предыдущий, но в нем появился и ввод.

;консольное приложение consol2
.386P
;плоская модель
.MODEL FLAT, stdcall
;константы
STD_OUTPUT_HANDLE equ -11
STD_INPUT_HANDLE equ -10
;прототипы внешних процедур
EXTERN GetStdHandle@4:NEAR
EXTERN WriteConsoleA@20:NEAR
EXTERN ReadConsoleA@20:NEAR
EXTERN ExitProcess@4:NEAR 
;директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
;------------------------------------------------
;сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
;буфер ввода
    BUF DB 100 DUP(?)
    LENS DD ? ;количество введенный символов
    RES DD ?
    H1 DD ?
    H2 DD ?
_DATA ENDS
;сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
    ;получить HANDLE вывода
    PUSH STD_OUTPUT_HANDLE
    CALL GetStdHandle@4  
    MOV H1,EAX
    ;получить HANDLE ввода
    PUSH STD_INPUT_HANDLE
    CALL GetStdHandle@4 
    MOV H2,EAX
    ;ввод информации
    PUSH OFFSET RES ;резерв
    PUSH OFFSET LENS ;введено символов
    PUSH 100 ;длина буфера
    PUSH OFFSET BUF ;адрес буфера
    PUSH H2 ;HANDLE ввода
    CALL ReadConsoleA@20
    ;вывести строку
    PUSH OFFSET RES ;резерв
    PUSH OFFSET LENS ;выведено символов
    PUSH LENS ;длина строки
    PUSH OFFSET BUF ;адрес строки
    PUSH H1 ;HANDLE вывода
    CALL WriteConsoleA@20  
    PUSH 0
    CALL ExitProcess@4 
  _TEXT ENDS
END START

Урок наш затянулся, но я хочу рассказать еще и о том, как получать параметры командной строки. Это очень важно, т.к. консольную программу часто запускают с параметрами.  Для получения самой командной строки используется функция LPTSTR GetCommandLine(), которая возвращает указатель на стрроку запуска программы. Строка заканчивается нулем, а первым параметром в ней идет полное имя самой запускаемой программы.  Ну что же, пора заканчивать, а в качестве упражнения напишите процедуру, для получения параметра командной строки. Предполагается, что параметры отделяются друг от друга прбелами.  Процедура получает два параметра: указатель на строку, куда будет помещен параметр и номер параметра, причем параметр с номером 0 это имя запущенной программы.  Мы приведем текст процедуры в начале следующего урока. 

Назад