Вторник, 22.07.2025, 04:45 Приветствую Вас Гость

On-line: Книги, учебники, статьи

Главная | Регистрация | Вход | RSS

Глава 2. Консольные приложения(1)

Глава 2. Консольные приложения

I

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

Надо сказать, что в книге автора "Assembler. Учебный курс" [1] излагается несколько экзотический способ трансляции консольных приложений, но связано это было с отсутствием у меня в то время нового инструментария. В данной книге мы используем MASM32 6.14 и TASM32 5.0. Здесь все достаточно просто.

Для MASM:

ml /с /coff cons1.asm
link /subsystem:console cons1.obj

Для TASM32:

TASM32 /ml cons1.asm
tlink32 /ap cons1.obj

Как и раньше, мы предполагаем, что библиотеки будут указываться при помощи директивы includelib. Ниже на Рис. 2.2.1 и Рис. 2.2.2 представлено простое консольное приложение для MASM и TASM соответственно. Для вывода текстовой информации используется функция API WriteConsoleA, параметры которой (слева направо) имеют следующий смысл.

  • 1-й параметр - дескриптор буфера вывода консоли, который может быть получен при помощи функции GetStdHandle.
  • 2-й параметр - указатель на буфер, где находится выводимый текст.
  • 3-й параметр - количество выводимых символов.
  • 4-й параметр - указывает на переменную DWORD, куда будет помещено количество действительно выведенных символов.
  • 5-й параметр - резервный параметр, должен быть равен нулю.

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

.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'
; строка в DOS-овской кодировке
 STR1 DB "Консольное приложение",0
 LENS DD ? ; количество выведенных символов
 RES DD ?
_DATA ENDS

; сегмент кода 
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START: 
; получить HANDLE вывода
 PUSH STD_OUTPUT_HANDLE
 CALL GetStdHandle@4
; длина строки
 PUSH OFFSET STR1
 CALL LENSTR
; вывести строку 
 PUSH OFFSET RES ; резерв
 PUSH OFFSET LENS ; выведено символов
 PUSH EBX ; длина строки
 PUSH OFFSET STR1 ; адрес строки
 PUSH EAX ; HANDLE вывода
 CALL WriteConsoleA@20
 PUSH 0 
 CALL ExitProcess@4
; строка - [EBP+08H]
; длина в EBX
LENSTR PROC
 PUSH EBP
 MOV EBP,ESP
 PUSH EAX 
;------------------------------
 CLD 
 MOV EDI, DWORD PTR [EBP+08H]
 MOV EBX,EDI 
 MOV ECX,100 ; ограничить длину строки
 XOR AL,AL 
 REPNE SCASB ; найти символ 0
 SUB EDI,EBX ; длина строки, включая 0
 MOV EBX,EDI
 DEC EBX
;------------------------------
 POP EAX
 POP EBP
 RET 4
LENSTR ENDP
_TEXT ENDS
END START 

Рис. 2.2.1. Простое консольное приложение для MASM32.

.386P
; плоская модель
.MODEL FLAT, stdcall

; константы
STD_OUTPUT_HANDLE equ -11

; прототипы внешних процедур
EXTERN GetStdHandle:NEAR
EXTERN WriteConsoleA:NEAR
EXTERN ExitProcess:NEAR 

; директивы компоновщику для подключения библиотек
includelib c:\tasm32\lib\import32.lib

;------------------------------------------------------------
; сегмент данных 
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
; строка в DOS-овской кодировке
 STR1 DB "Консольное приложение",0
 LENS DD ?
; количество выведенных символов
 RES DD ?
_DATA ENDS

; сегмент кода 
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START: 
; получить HANDLE вывода
 PUSH STD_OUTPUT_HANDLE
 CALL GetStdHandle 
; длина строки
 PUSH OFFSET STR1
 CALL LENSTR
; вывести строку
 PUSH OFFSET RES ; резерв
 PUSH OFFSET LENS ; выведено символов
 PUSH EBX ; длина строки
 PUSH OFFSET STR1 ; адрес строки
 PUSH EAX ; HANDLE вывода
 CALL WriteConsoleA
 PUSH 0
 CALL ExitProcess 
; строка - [EBP+08H] 
; длина в EBX

LENSTR PROC
 PUSH EBP
 MOV EBP,ESP
 PUSH EAX
; ----------
 CLD
 MOV EDI, DWORD PTR [EBP+08H]
 MOV EBX,EDI
 MOV ECX,100 ; ограничить длину строки
 XOR AL,AL
 REPNE SCASB ; найти символ 0
 SUB EDI, EBX ; длина строки, включая 0 
 MOV EBX,EDI
 DEC EBX
; ----------
 POP EAX
 POP EBP
 RET 4
LENSTR ENDP
_TEXT ENDS
END START

Рис. 2.2.2. Простое консольное приложение для TASM32.

Надо сказать, что, поскольку информация выводится в консольном окне, кодировка всех строковых констант должна быть DOS-овской. В дальнейшем разъяснится вопрос, как осуществить перекодировку программным путем.

Прокомментируем теперь приведенные выше программы. При запуске их из командной строки, например из Far'a, в строку выводится сообщение "Консольное приложение". При запуске программы как Windows-приложения консольное окно появляется лишь на секунду. В чем тут дело? Дело в том, что консольные приложения могут создать свою консоль. В этом случае весь ввод-вывод будет производиться в эту консоль. Если же приложение консоль не создает, то здесь может возникнуть двоякая ситуация: либо наследуется консоль, в которой программа была запущена, либо Windows создает для приложения свою консоль.

II

Рассмотрим несколько простых консольных функций и их применение. Во-первых, работать с чужой консолью не всегда удобно. А для того чтобы создать свою консоль, используется функция AllocConsole. По завершении программы все выделенные консоли автоматически освобождаются. Однако это можно сделать и принудительно, используя функцию FreeConsole. Для того чтобы получить дескриптор консоли, используется уже знакомая Вам функция GetStdHandle, аргументом которой может являться следующая из трех констант:

STD_INPUT_HANDLE equ -10 ; для ввода
STD_OUTPUT_HANDLE equ -11 ; для вывода
STD_ERROR_HANDLE equ -12 ; для сообщения об ошибке

Следует отметить, что один процесс может иметь только одну консоль, поэтому выполнение в начале программы FreeConsole обязательно. При запуске программы в "чужой" консоли она наследует эту консоль, поэтому, пока мы не выполним функцию FreeConsole, новой консоли не создать - чужой консоли эта функция закрыть не может.

Для чтения из буфера консоли используется функция ReadConsole. Значения параметров этой функции (слева-направо)28 следующие:

  • 1-й, дескриптор входного буфера.
  • 2-й, адрес буфера, куда будет помещена вводимая информация.
  • 3-й, длина этого буфера.
  • 4-й, количество фактически прочитанных символов.
  • 5-й, зарезервировано.

Установить позицию курсора в консоли можно при помощи функции SetConsoleCursorPosition со следующими параметрами:

  • 1-й, дескриптор входного буфера консоли.
  • 2-й, структура COORD:
COORD STRUC
 Х WORD ?
 Y WORD ?
COORD ENDS

Хочу лишний раз подчеркнуть, что вторым параметром является не указатель на структуру (что обычно бывает), а именно структура. На самом деле для ассемблера это просто двойное слово (DWORD), у которого младшее слово - координата X, а старшее слово — координата Y.

Установить цвет выводимых букв можно с помощью функции SetConsoleTextAttribute. Первым параметром этой функции является дескриптор выходного буфера консоли, а вторым - цвет букв и фона. Цвет получается путем комбинации (сумма или операция "ИЛИ") двух или более из представленных ниже констант. Причем возможна "смесь" не только цвета и интенсивности, но и цветов (см. программа ниже).

FOREGROUND_BLUE equ 1h ; синий цвет букв
FOREGROUND_GREEN equ 2h ; зеленый цвет букв
FOREGROUND_RED equ 4h ; красный цвет букв
FOREGROUND_INTENSITY equ 8h ; повышенная интенсивность
BACKGROUND_BLUE equ 10h ; синий свет фона
BACKGROUND_GREEN equ 20h ; зеленый цвет фона
BACKGROUND_RED equ 40h ; красный цвет фона
BACKGROUND_INTENSITY equ 80h ; повышенная интенсивность

Для определения заголовка окна консоли используется функция SetConsoleTitle, единственным параметром которой является адрес строки с нулем на конце. Здесь следует оговорить следующее: если для вывода в само окно консоли требовалась DOS-кодировка, то для установки заголовка требуется Windows-кодировка. Чтобы покончить с этой проблемой раз и навсегда, посмотрим, как это можно решить средствами Windows.

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

Мы рассмотрели несколько консольных функций, всего их около пятидесяти. Нет нужды говорить обо всех этих функциях. О некоторых из них я еще скажу, но читатель, я думаю, по приведенным в книге примерам и обсуждениям сможет сам использовать в своих программах другие консольные функции. Замечу только, что для большинства консольных функций характерно то, что при правильном их завершении возвращается ненулевое значение. В случае ошибки в EAX помещается ноль.

Ну что же, пора приступать к разбору следующих примеров.

.386P
; плоская модель
.MODEL FLAT, stdcall

; константы
STD_OUTPUT_HANDLE equ -11
STD_INPUT_HANDLE equ -10

; атрибуты цветов
FOREGROUND_BLUE equ 1h ; синий цвет букв
FOREGROUND_GREEN equ 2h ; зеленый цвет букв
FOREGROUND_RED equ 4h ; красный цвет букв
FOREGROUND_INTENSITY equ 8h ; повышенная интенсивность
BACKGROUND_BLUE equ 10h ; синий свет фона
BACKGROUND_GREEN equ 20h ; зеленый цвет фона
BACKGROUND_RED equ 40h ; красный цвет фона
BACKGROUND_INTENSITY equ 80h ; повышенная интенсивность

COL1 = 2h+8h ; цвет выводимого текста
COL2 = 1h+2h+8h ; цвет выводимого текста 2

; прототипы внешних процедур
EXTERN GetStdHandle@4:NEAR
EXTERN WriteConsoleA@20:NEAR
EXTERN SetConsoleCursorPosition@8:NEAR
EXTERN SetConsoleTitleA@4:NEAR
EXTERN FreeConsole@0:NEAR
EXTERN AllocConsole@0:NEAR
EXTERN CharToOemA@8:NEAR
EXTERN SetConsoleCursorPosition@8:NEAR
EXTERN SetConsoleTextAttribute@8:NEAR
EXTERN ReadConsoleA@20:NEAR
EXTERN SetConsoleScreenBufferSize@8:NEAR
EXTERN ExitProcess@4:NEAR 

; директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib

;------------------------------------------------------------
COOR STRUC
 X WORD ?
 Y WORD ?
COOR ENDS

; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA' 
 HANDL DWORD ?
 HANDL1 DWORD ? 
 STR1 DB "Введите строку: ",13,10,0
 STR2 DB "Простой пример работы консоли",0
 BUF DB 200 dup (?) 
 LENS DWORD ? ; количество выведенных символов
 CRD COOR <?>
_DATA ENDS

; сегмент кода 
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
; перекодируем строку
 PUSH OFFSET STR1
 PUSH OFFSET STR1
 CALL CharToOemA@8 
; образовать консоль
; вначале освободить уже существующую 
 CALL FreeConsole@0
 CALL AllocConsole@0
; получить HANDL1 ввода 
 PUSH STD_INPUT_HANDLE
 CALL GetStdHandle@4
 MOV HANDL1, EAX
; получить HANDL вывода
 PUSH STD_OUTPUT_HANDLE
 CALL GetStdHandle@4
 MOV HANDL, EAX 
; установить новый размер окна консоли 
 MOV CRD.X, 100
 MOV CRD.Y, 25
 PUSH CRD
 PUSH EAX 
 CALL SetConsoleScreenBufferSize@8
; задать заголовок окна консоли
 PUSH OFFSET STR2 
 CALL SetConsoleTitleA@4
; установить позицию курсора 
 MOV CRD.X,0
 MOV CRD.Y,10
 PUSH CRD
 PUSH HANDL 
 CALL SetConsoleCursorPosition@8
; задать цветовые атрибуты выводимого текста
 PUSH COL1
 PUSH HANDL 
 CALL SetConsoleTextAttribute@8
; вывести строку
 PUSH OFFSET STR1
 CALL LENSTR ; в EBX длина строки
 PUSH 0
 PUSH OFFSET LENS
 PUSH EBX
 PUSH OFFSET STR1
 PUSH HANDL
 CALL WriteConsoleA@20
; ждать ввод строки
 PUSH 0
 PUSH OFFSET LENS
 PUSH 200
 PUSH OFFSET BUF
 PUSH HANDL1
 CALL ReadConsoleA@20 
; вывести полученную строку
; вначале задать цветовые атрибуты выводимого текста
 PUSH COL2
 PUSH HANDL
 CALL SetConsoleTextAttribute@8
;------------------------------------------------------------
 PUSH 0
 PUSH OFFSET LENS
 PUSH [LENS] ; длина вводимой строки
 PUSH OFFSET BUF
 PUSH HANDL
 CALL WriteConsoleA@20
; небольшая задержка
 MOV ECX,01FFFFFFFH
L1:
 LOOP L1
; закрыть консоль
 CALL FreeConsole@0
 CALL ExitProcess@4

; строка - [EBP+08H]
; длина в EBX
LENSTR PROC
 ENTER 0,0
 PUSH EAX
;--------------
 CLD
 MOV EDI, DWORD PTR [EBP+08H]
 MOV EBX, EDI
 MOV ECX, 100 ; ограничить длину строки
 XOR AL,AL
 REPNE SCASB ; найти символ 0
 SUB EDI, EBX ; длина строки, включая 0
 MOV EBX, EDI
 DEC EBX
;--------------
 POP EAX
 LEAVE
 RET 4
LENSTR ENDP
_TEXT ENDS
END START

Рис. 2.2.3. Пример создания собственной консоли.




Вход на сайт
Поиск
Календарь
«  Июль 2025  »
ПнВтСрЧтПтСбВс
 123456
78910111213
14151617181920
21222324252627
28293031
Архив записей
Наш опрос
Как Вам удобнее??
Всего ответов: 341
Мини-чат
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0