Сборник по задачам и примерам Assembler

Минимальная программа консольного приложения



Минимальная программа консольного приложения

Минимальная программа консольного приложения на ассемблере выглядит так:

.model flat.STDCALL ;модель памяти flat.

i ncludeWindowConA.i nc

:Обьявление внешними используемых в данной программе функций Win32 (ASCII):

extrn AllocConsole:PROC

extrn SetConsoleTitleA:PROC

extrn ExitProcess:PROC

.data

TitleText db 'Минимальное консольное приложение Win32'.0

.code

start proc near :точка входа в программу:

:запрос консоли



call AllocConsole проверить успех запроса консоли

test eax.eax

jz exit :неудача :выведем заголовок окна консоли SetConsoleTitle:

push offset TitleText

call SetConsoleTitleA проверить успех вывода заголовка

test eax.eax

jz exit ;неудача

exit: :выход из приложения

:готовим вызов VOID ExitProcess(UINT uExitCode)

push 0

call ExitProcess start endp end start

Если убрать комментарии, то кода будет совсем немного. В нем представлены вызовы трех функций: AllocConsole, SetConsoleTitle, ExitProcess.

Первой функцией консольного приложения должна быть функция запроса консоли AllocConsole.

B00L AllocConsole(VOID);

Для вызова функции Al I ocConsol e не требуется никаких параметров. В случае успеха функция Al I ocConsol e возвращает ненулевое значение, при неудаче — нуль. Выделенная консоль представляет собой типичное для Windows окно. Процесс в конкретный момент времени может использовать одну консоль. Если ему нужно запустить еще одну консоль, то прежняя должна быть закрыта или освобождена с помощью функции FreeConsole.

B00L FreeConsole(VOID);

В случае успеха функция FreeConsol e возвращает ненулевое значение, при неудаче — нуль.

При завершении процесса выделенная процессу консоль освобождается автоматически. В нашем случае использован именно этот вариант закрытия консоли — функцией ExitProcess.

VOID ExitProcesstUINT uExitCode):

Функции ExitProcess передается код завершения процесса и всех завершаемых цепочек в этом процессе. Проанализировать этот код можно с помощью функ-

ций GetExitCodeProcess и GetExitCodeThread. В общем случае в различных ветвях кода может быть несколько точек выхода с вызовом ExitProcess. Задавая различные значения кода завершения, можно таким образом идентифицировать причину завершения процесса.


Окно консоли может иметь заголовок, для отображения которого предназначена функция SetConsoleTitle.

B00L SetConsolеTitle(LPCTSTR lpConsoleTitle) ;

Функция SetConsoleTitle имеет один параметр — указатель на строку с заголовком консоли, заканчивающуюся нулем.

Организация высокоуровневого консольного ввода-вывода

Для высокоуровневого ввода-вывода приложение может использовать файловые функции ReadFile и WriteFile, а также функции консольного ввода-вывода Read-Console и WriteConsole. Эти функции обеспечивают косвенный доступ к входному и экранным буферам пульта. Физически эти функции фильтруют записи входного буфера консоли так, чтобы возвратить ввод как поток символов, игнорируя все другие записи с расширенной информацией о мыши, клавиатуре и изменении размеров окна консоли. Отфильтрованный поток символов отображается в окне консоли начиная с текущей позиции курсора. Существуют два важных отличия в использовании пар функций ReadFile\WriteFile и ReadConsoleNWrite-Console.

  • Поддержка символов Unicode и ANSI. Консольные функции (ReadConso-le\WriteConsole) поддерживают эти наборы, а файловые (ReadFile\Write-File) — нет.


  • Функции файлового ввода-вывода могут использоваться как для обращения к файлам, так и к именованным каналам, устройствам, присоединенным к последовательному интерфейсу. Консольные функции ввода-вывода можно использовать только с тремя дескрипторами стандартного ввода-вывода (см. ниже описание функций ReadConsole\WriteConsole).


  • Из вышесказанного следует, что функции высокоуровневого ввода-вывода обеспечивают простой способ обмена (чтения-записи) потоков символов с консолью.

    Операция чтения высокого уровня реализуется функцией ReadConsole, которая получает входные символы из буфера ввода консоли и сохраняет их в указанном буфере.

    B00L ReadConsoleCHANDLE hConsolelnput. LPVOID ipBuffer. DWORD nNumberOfCharsToRead. LPDWORD lpNumberOfCharsRead. LPVOID lpReserved);

    Параметры этой функции означают следующее:

  • hConsolelnput — дескриптор входного потока консоли;




  • IpBuffer — указатель на строку, в которую будет записана вводимая строка символов;


  • nNumberOfCharsToRead — размер буфера, указанного lpBuffer;


  • ipNumberOfCharsRead — количество действительно введенных символов;


  • lpReserved — этот параметр не используется, поэтому должен задаваться

    как NULL.


  • Операция записи высокого уровня реализуется функцией WriteConsole, которая извлекает символы из указанного буфера и записывает их в экранный буфер, начиная с текущей позиции курсора и продвигая ее по мере записи символов. B00L WriteConsoleCHANDLE hConsoleOutput. CONST VOID *lpBuffer.

    DWORD nNumberOfCharsToWrite. LPDWORD
    ipNumberOfCharsWritten. LPVOID lpReserved);

    Параметры этой функции означают следующее:

  • hConsoleOutput — дескриптор выходного потока консоли;


  • lpBuffer — указатель на выводимую строку;


  • nNumberOfCharsToWrite — размер буфера, указанного IpBuffer;


  • IpNumberOfCharsWritten — количество действительно выведенных символов;


  • lpReserved — этот параметр не используется, поэтому должен задаваться

    как NULL.


  • Для своей работы эти и некоторые другие консольные функции требуют получения стандартных дескрипторов ввода-вывода. Значения этих дескрипторов присваиваются параметрам hConsolelnput и hConsoleOutput. По умолчанию стандартный дескриптор ввода связан с клавиатурой, стандартный дескриптор вывода—с экраном. Получить стандартный дескриптор ввода-вывода можно с помощью функции GetStdHandle.

    HANDLE GetStdHand 1 e(DWORD nStdHandle):

    На вход функции GetStdHandle должно быть подано одно из следующих значений:

  • STD_INPUT_HANDLE = -10 — дескриптор стандартного входного потока;


  • STD_OUTPUT_HANDLE = -11 — дескриптор стандартного выходного потока;


  • STD_ERROR_HANDLE - -12 — дескриптор стандартного потока ошибок.


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

  • эхо-контроль вводимых символов на экране из активного экранного буфера;




  • ввод строки, окончание операции чтения которой происходит при нажатии клавиши Enter;


  • автоматическая обработка некоторых символов, вводимых с клавиатуры:

    перевода каретки, нажатия клавиш Ctrl+C и т. д.;


  • автоматическая обработка некоторых символов, выводимых на экран: перевода строки и каретки, возврата на один символ и т. д.


  • Функция SetConsol eCursorPosition предназначена для указания позиции, с которой начинается выполнение операций чтения-записи в окно консоли. B00L SetConsoleCursorPosition(HANDLE hConsoleOutput. COORD dwCursorPosition); Параметрами этой функции являются стандартный дескриптор вывода hCon-[' soleOutput, полученный функцией GetStdHandle, и указатель на структуру COORD с координатами новой позиции курсора:

    COORD struc x dw 0 у dw 0 ends

    По умолчанию цветовое оформление окна консоли достаточно унылое — черный фон, белый текст. Внести разнообразие во внешний вид окна консоли поможет функция SetConsoleTextAttribute, с помощью которой можно изменить установки цвета по умолчанию для текста и фона.

    B00L SetConsoleTextAttributetHANDLE hConsoleOutput. WORD wAttributes):

    Первый параметр — без комментариев, второй определяет цвет текста и фона. Второй параметр формируется как логическое ИЛИ следующих значений:

  • FOREGROUND_BLUE=0001h - синий текст;


  • FOREGROUND_GREEN=0002h - зеленый текст;


  • FOREGROUND_RED=0004h — красный текст;


  • FOREGROUND_INTENSITY=0008h — текст повышенной яркости;


  • BACKGROUND_BLUE=0010h - голубой фон;


  • BACKGROUND_GREEN=0020h - зеленый фон;


  • BACKGROUND_RED=0040h - красный фон;


  • BACKGROUND_INTENSITY=0080h — фон повышенной яркости.


  • Для задания белого цвета складываются три компоненты, для задания черного — компоненты не задаются вовсе.

    Пример программы ввода-вывода в консоль

    Для демонстрации использования функций высокоуровневого ввода-вывода в окно консоли разработаем программу, которая вводит с клавиатуры строку и отображает ее в заголовке окна консоли, а затем выводит эту строку в окне консоли с изменением текущей позиции курсора и цвета текста.



    :prg05_11.asm - программа ввода-вывода в консоль с изменением атрибутов выводимого текста

    !

    .data

    .code

    start proc near -.точка входа в программу:

    ..........

    :получим стандартные дескрипторы ввода-вывода

    push STD_OUTPUT_HANDLE

    call GetStdHandle

    movdOut.eax :dOut-fleCKpnnTop вывода консоли

    push STD_INPUT_HANDLE

    call GetStdHandle

    mov din.eax idln-дескриптор ввода консоли :введем строку

    .¦установим курсор в позицию (2,6)

    mov con.хх.2

    mov con.yy,6

    push con

    push dOut

    call SetConsoleCursorPosition cmp eax. 0

    jz exit ;если неуспех

    push 0

    push offset NumWri количество действительно введенных символов

    push 80 :размер буфера TitleText для ввода

    push offset TitleText

    push din

    call ReadConsoleA

    cmp eax, 0

    jz exit :если неуспех

    :выведем введенную строку в заголовок окна консоли:

    push offset TitleText

    call SetConsoleTitleA проверить успех вывода заголовка

    test eax.eax

    jz exit ;неудача

    :выведем строку в окно консоли с различных позиций и
    с разными цветами установим курсор в позицию (2.5)

    mov ecx.10 ;строку выведем 10 раз

    mov bl.10000001b начальные атрибуты

    ml: push ecx

    inc con.xx

    inc con.yy

    push con

    push dOut

    call SetConsoleCursorPosition

    cmp eax.O

    jz exit :если неуспех ;определим атрибуты выводимых символов -
    будем получать их циклически сдвигом - регистр

    BL

    хог еах.еах

    rol Ы.1

    mov al.bl

    push eax

    push dOut

    call SetConsoleTextAttribute

    cmp eax.O

    jz exit ;если неуспех :вывести строку

    push 0

    push offset NumWri действительное количество выведенных на экран

    push NumWri ;длина строки для вывода на экран

    push offset TitleText :адрес строки для вывода на экран

    push dOut

    call WriteConsoleA

    cmp eax.O

    jz exit :если неуспех pop ecx

    loop ml

    exit: :выход из приложения

    Каждый консольный процесс имеет свой собственный список функций-обработчиков, которые вызываются системой, когда происходят определенные собы тия, например при активном окне консоли пользователь нажимает комбинации клавиш Ctrl+C, Ctrl+Break или Ctrl+Close. При запуске консольного приложения список функций-обработчиков содержит только заданную по умолчанию функцию-обработчик, которая вызывает функцию ExitProcess. Консольный процесс может добавлять или удалять дополнительные функции-обработчики, вызывая функцию SetConsoleCtrlHandler.



    B00L SetConsoleCtrlHandler(PHANDLER_ROUTINE HandlerRoutine. B00L Add): Данная функция имеет два параметра:

  • HandlerRoutine — указатель на определенную приложением функцию HandlerRoutine, которая должна быть добавлена или удалена;


  • Add — логическое значение, которое означает: 1 — функция должна быть

    добавлена, 0 — функцию необходимо удалить.


  • Функция HandlerRoutine — это определенная приложением функция обратного вызова. Консольный процесс использует эту функцию, чтобы обработать нажатия клавиш управления. На самом деле HandlerRoutine — идентификатор-заполнитель для определенного приложением имени функции. B00L WINAPI HandIerRoutine(DWORD dwCtrlType):

    Параметр DwCtrlType определяет тип сигнала управления, получаемого обработчиком. Этот параметр может принимать одно из следующих значений:

  • CTRL_C_EVENT=O — сигнал, имитирующий нажатие клавиш Ctrl+C, может быть получен из двух источников: с клавиатуры или как сигнал, сгенерированный функцией GenerateConsoleCtrl Event;


  • CTRL_BREAK_EVENT=1 — сигнал имитирующий нажатие клавиш Ctrl+Break, может быть получен из двух источников: с клавиатуры или как сигнал, сгенерированный функцией GenerateConsoleCtrl Event;


  • CTRL_CL0SE_EVENT=2 — сигнал, который система посылает всем процессам, подключенным к данному консольному приложению, когда пользователь его закрывает (либо выбирая пункт Close в системном меню окна консоли, либо щелкая на кнопке завершения задачи в диалоговом окне Менеджера задач);


  • CTRL_LOGOFF_EVENT=5 — сигнал, который посылается всем консольным процессам, когда пользователь завершает работу в системе (этот сигнал не указывает, какой именно пользователь завершает работу);


  • CTRL_SHUTD0WN_EVENT=6 — сигнал, который система посылает всем консольным процессам при подготовке к выключению машины. Функция HandlerRoutine должна возвратить логическое значение: 1 — если она обрабатывает конкретный сигнал управления; 0 — для обработки полученного события будет использоваться другая функция-обработчик HandlerRoutine из списка функций-обработчиков для этого процесса (то есть включенная в этот список раньше данной функции).




  • Как уже было упомянуто, каждый консольный процесс может определить несколько функций HandlerRoutine, которые связываются в цепочку. Первоначально этот список содержит только заданную по умолчанию функцию обработчика, которая вызывает функцию ExitProcess и, как результат, приводит к завершению текущего консольного приложения. Консольный процесс добавляет или удаляет

    Работа с консолью в среде Windows 233

    дополнительные функции обработчика, вызывая функцию SetConsoleCtrl Handler, которая не затрагивает список функций-обработчиков для других процессов. Когда консольный процесс принимает любой из сигналов управления (см. выше), то вызывается последняя зарегистрированная функция-обработчик, если она не возвращает 1, то управление передается следующему (предыдущему) зарегистрированному обработчику и т. д., до тех пор пока один из обработчиков не возвратит 1. Если ни один из обработчиков этого не сделал, то вызывается обработчик, заданный по умолчанию.

    Установка обработчиков для сигналов CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT и

    CTRL_SHUTDOWN_EVENT дает процессу возможность выполнить специфичные для него

    действия по корректному завершению приложения. Пользовательская функция

    HandlerRoutine может быть вызвана для того, чтобы выполнить следующие действия:

  • вызвать функцию ExitProcess для завершения процесса;


  • возвратить 0 (ложь) — это означает, что завершение приложения должен

    выполнить обработчик, заданный по умолчанию;


  • В возвратить 1 — в этом случае никакие другие функции-обработчики не вызываются, а система отображает всплывающее диалоговое окно с запросом о необходимости завершения процесса; система также отображает диалоговое окно, если процесс не отвечает определенное время (5 секунд для CTRLCLOSEEVENT и 20 секунд для CTRLLOGOFFEVENT и CTRLSHUTDOWNEVENT); процесс может использовать функцию SetProcessShutdownParameters, чтобы запретить системе отображать последнее диалоговое окно, в этом случае система просто заканчивает процесс, когда HandlerRoutine возвращает истину или когда истекает определенный период времени. Ниже приведен пример пользовательского обработчика события — ввода комбинации Ctrl+C или Ctrl+Break. За основу взята предыдущая программа.





    :prg05_12.asm - программа, демонстрирующая использование пользовательского обработчика события.

    .data

    Text_CTRL_C db "Нажаты CTRL+C"

    Len_Text_CTRL=$-Text_CTRL_C

    TextJREAK db "Нажаты CTRL+BREAK"

    Len_BREAK=$-Text_BREAK

    .code

    CtrlHandler proc

    arg @@dwCtrlType:DWORD

    uses ebx.edi. esi ;эти регистры обязательно должны сохраняться

    :анализируем тип сигнала управления

    cmp @@dwCtrlType.CTRL_C_EVENT

    je h_CTRL_C_EVENT

    cmp (a@dwCtrlType.CTRL_BREAK_EVENT

    je h_CTRL_BREAK_EVENT

    jmp h_default

    h_CTRL_C_EVENT: :при нажатии CTRL+C выводим сообщение: установим курсор

    call SetConsoleCursorPosition :вывести строку Text_CTRL_C call WriteConsoleA

    ; возвращаем признак обработки

    mov eax.l

    jmp exit_CtrlHandler h_CTRL_BREAK_EVENT:
    ;при нажатии CTRL+BREAK выводим сообщение:

    установим курсор

    call SetConsoleCursorPosition : вывести строку

    call WriteConsoleA

    ;возвращаем признак обработки

    mov eax.l

    jmp exit_CtrlHandler

    h_default: mov eax.Offffffffh;возвращаем остальное не обрабатываем
    exit_CtrlHandler: ret CtrlHandler endp start proc near ;точка входа в программу:

    :работаем .........

    :получим стандартные дескрипторы ввода-вывода

    установим функцию-обработчик сигналов управления

    push TRUE

    push offset cs: CtrlHandler

    call SetConsoleCtrlHandler

    onp eax. 0

    jz exit :если неуспех ;введем строку в буфер TitleText установим курсор в позицию (2.6)

    call SetConsoleCursorPosition call ReadConsoleA

    :выведем введенную строку в заголовок окна консоли: push offset TitleText call SetConsoleTitleA

    :выведем строку в окно консоли с различных позиций и с разными цветами

    mov ecx.10 :строку выведем 10 раз

    mov bl.10000001b начальные атрибуты ml: push ecx установим курсор в позицию

    call SetConsoleCursorPosition

    определим атрибуты выводимых символов - будем получать их циклически сдвигом регистра BL хог еах.еах

    rol Ы .1

    mov al ,Ы

    push eax

    push d0ut

    call SetConsoleTextAttribute . :вывести строку TitleText

    call WriteConsoleA cmp eax.0

    jz exit ;если неуспех pop ecx



    loop ml

    Относительно этой программы можно сделать два замечания. Первое касается функции Handl erRoutine, которая в нашей программе называется Ctrl Handler. Как упоминалось, эта функция является функцией обратного вызова. Ее вызов производится при возникновении определенных событий неявно — из системы Windows. По структуре и алгоритму работы она аналогична оконной функции, которую мы рассматривали в уроке 18 «Создание Windows-приложений на ассемблере» учебника. Поэтому за всеми подробностями отсылаем читателя к этому материалу. Второе замечание касается порядка отладки приложений, содержащих определяемые пользователем функции (процедуры) обратного вызова. Первое, что нужно сделать в процессе пошагового выполнения программы в отладчике, — выяснить адрес процедуры обратного вызова. В программе выше это можно сделать, выяснив, какое значение будет помещено в стек при выполнении команд:

    ..........

    [установим функцию-обработчик сигналов управления

    push TRUE

    push offset cs: Ctrl Handler

    call SetConsoleCtrlHandler

    cmp eax. 0

    jz exit [если неуспех

    .........

    После этого, сделав активным окно отладчика CPU (выбрав в меню команду

    View CPU), необходимо установить указатель мыши в окно с командами процес-; сора и щелкнуть правой кнопкой мыши. В появившемся контекстном меню вы-

    бер*етс пункт Goto... В результате этих действий отладчик отобразит диалоговое ¦ окно, в которое необходимо внести адрес программы-обработчика Ctrl Handler. ; В результате этого в верхней части окна команд отобразится первая команда [' процедуры Ctrl Handler. Установите на нее курсор и нажмите клавишу F4. Все, S программа начнет выполняться по своему алгоритму. При нажатии пользователем

    управляющих комбинаций клавиш, допустимых функцией Handl erRoutine, управ-I ление будет передано этой функции, и вы сможете произвести ее отладку.


    Содержание раздела