Четверг, 24.07.2025, 22:23 Приветствую Вас Гость

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

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

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

После того как вы познакомились с программой на Рис. 2.2.4, давайте ее подробнее обсудим.

Начнем с функции wsprintfA. Как я уже заметил, функция необычная.

  1. Она имеет переменное число параметров. Первые два параметра обязательны. Вначале идет указатель на буфер, куда будет скопирована результирующая строка. Вторым идет указатель на форматную строку. Форматная строка может содержать текст, а также формат выводимых параметров. Поля, содержащие информацию о параметре, начинаются с символа "%". Формат этих полей в точности соответствует формату полей, используемых в стандартных Си-функциях printf, sprintf и др. Исключением является отсутствие в формате для функции wsprintf вещественных чисел. Нет нужды излагать этот формат, заметим только, что каждое поле в форматной строке соответствует параметру (начиная с третьего). В нашем случае форматная строка была равна: "Координаты: %u %u". Это означало, что далее в стек будет отправлено два числовых параметра типа WORD. Конечно, в стек мы отправили два двойных слова, позаботившись лишь о том, чтобы старшие слова были обнулены. Для такой операции очень удобна команда микропроцессора MOVZX, которая копирует второй операнд в первый так, чтобы биты старшего слова были заполнены нулями. Если бы параметры были двойными словами, то вместо поля %u мы бы поставили %lu. В случае, если поле форматной строки определяет строку-параметр, например "%S", в стек следует отправлять указатель на строку (что естественно).
  2. Поскольку функция "не знает", сколько параметров может быть в нее отправлено, разработчики не стали усложнять текст этой функции, и оставили нам проблему освобождения стека30. Это производится командой ADD ESP,N. Здесь N - это количество освобождаемых байтов.

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

По обыкновению отмечу, как откомпилировать данную программу в TASM32. Как обычно, удаляем все значки @N, указываем библиотеку import32.lib и наконец wsprintfA меняем на _wsprintfA.


29 В этой связи не могу не отметить, что встречал в литературе по ассемблеру (!) утверждение, что все помещаемые в стек для этой функции параметры являются указателями. Как видим, вообще говоря, это не верно.

30 Компилятор Си, естественно, делает это за нас.


IV

В последнем разделе главы мы рассмотрим довольно редко освещаемый в литературе вопрос - таймеры в консольном приложении. Надо сказать, что мы несколько опережаем события и рассматриваем таймер в консольном приложении раньше, чем в приложении GUI (Graphic Universal Interface - так называются обычные оконные приложения).

Основным способом создания таймера является использование функции SetTimer. Позднее мы будем подробно о ней говорить. Таймер может быть установлен в двух режимах. Первый режим - это когда последний параметр равен нулю. В этом случае на текущее окно (его функцию) через равные промежутки времени, определяемые третьим параметром, будет приходить сообщение WM_TIMER. Во втором режиме последний параметр указывает на функцию, которая будет вызываться опять через равные промежутки времени. Однако для консольного приложения эта функция не подходит, так как сообщение WM_TIMER пересылается окну функцией DispatchMessage, которая используется в петле обработки сообщений. Но использование этой функции для консольных приложений проблематично.

Для консольных приложений следует использовать функцию timeSetEvent. Вот параметры этой функции:

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

Если функция завершилась удачно, то в EAX возвращается идентификатор таймера.

Сама вызываемая процедура получает также 5 параметров:

  • 1-й параметр - идентификатор таймера.
  • 2-й параметр - не используется.
  • 3-й параметр - параметр Dat (см. timeSetEvent).
  • 4 и 5-й параметры - не используются.

Для удаления таймера используется функция timeKillEvent, параметром которой является идентификатор таймера.

.386P
; плоская модель
.MODEL FLAT, stdcall
; константы
STD_OUTPUT_HANDLE equ -11
STD_INPUT_HANDLE equ -10
TIME_PERIODIC equ 1 ; тип вызова таймера

; атрибуты цветов
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 ; цвет выводимого текста

; прототипы внешних процедур
EXTERN wsprintfA:NEAR
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 timeSetEvent@20:NEAR
EXTERN timeKillEvent@4:NEAR 
EXTERN ExitProcess@4:NEAR 

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

COOR STRUC
 X WORD ?
 Y WORD ?
COOR ENDS

; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA' 
 HANDL DWORD ?
 HANDL1 DWORD ? 
 STR2 DB "Пример таймера в консольном приложении",0
 STR3 DB 100 dup (0) 
 FORM DB "Число вызовов таймера: %lu",0
 BUF DB 200 dup (?)
 NUM DWORD 0 
 LENS DWORD ? ; количество выведенных символов
 CRD COOR <?>
 ID DWORD ? ; идентификатор таймера
 HWND DWORD ?
_DATA ENDS

; сегмент кода 
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START: 
; образовать консоль
; вначале освободить уже существующую 
 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 
; задать заголовок окна консоли 
 PUSH OFFSET STR2
 CALL SetConsoleTitleA@4 
; задать цветовые атрибуты выводимого текста 
 PUSH COL1
 PUSH HANDL
 CALL SetConsoleTextAttribute@8 
; установить таймер 
 PUSH TIME_PERIODIC ; периодический вызов
 PUSH 0 
 PUSH OFFSET TIME ; вызываемая таймером процедура
 PUSH 0 ; точность вызова таймера
 PUSH 1000 ; вызов через одну секунду
 CALL timeSetEvent@20
 MOV ID, EAX 
; ждать ввод строки 
 PUSH 0
 PUSH OFFSET LENS
 PUSH 200 
 PUSH OFFSET BUF
 PUSH HANDL1
 CALL ReadConsoleA@20 
; закрыть таймер
 PUSH ID
 CALL timeKillEvent@4 
; закрыть консоль
 CALL FreeConsole@0
 PUSH 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 

; процедура вызывается таймером
TIME PROC 
 PUSHA ; сохранить все регистры
; установить позицию курсора 
 MOV CRD.X,0
 MOV CRD.Y,10 
 PUSH CRD
 PUSH HANDL 
 CALL SetConsoleCursorPosition@8
; заполнить строку STR3 
 PUSH NUM 
 PUSH OFFSET FORM
 PUSH OFFSET STR3
 CALL wsprintfA
 ADD ESP,12 ; восстановить стек
; перекодировать строку STR3 
 PUSH OFFSET STR3
 PUSH OFFSET STR3
 CALL CharToOemA@8 
; вывести строку с номером вызова таймера 
 PUSH OFFSET STR3
 CALL LENSTR
 PUSH 0 
 PUSH OFFSET LENS
 PUSH EBX 
 PUSH OFFSET STR3
 PUSH HANDL 
 CALL WriteConsoleA@20
 INC NUM 
 POPA
 RET 20 ; выход с освобождением стека
TIME ENDP
_TEXT ENDS
END START

Рис. 2.2.5. Таймер в консольном режиме.

Программа на Рис. 2.2.5 будет выводить в окно значение счетчика, которое будет каждую секунду увеличиваться на единицу.

Я начал данную главу с рассуждения о командной строке, но до сих пор не объявил, как работать с командной строкой. О, здесь все очень просто. Есть API-функция GetCommandLine, которая возвращает указатель на командную строку. Эта функция одинаково работает как для консольных приложений, так и для приложений GUI. Ниже представлена программа, печатающая параметры командной строки. Надеюсь, вы понимаете, что первым параметром является полное имя программы.

; программа вывода параметров командной строки
.386P
; плоская модель
.MODEL FLAT, stdcall

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

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

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

; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
 BUF DB 100 dup (0)
 LENS DWORD ? ; количество выведенных символов
 NUM DWORD ?
 CNT DWORD ?
 HANDL DWORD ?
_DATA ENDS

; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
; получить HANDLE вывода
 PUSH STD_OUTPUT_HANDLE
 CALL GetStdHandle@4
 MOV HANDL,EAX
; получить количество параметров
 CALL NUMPAR
 MOV NUM,EAX
 MOV CNT,0
;-------------------------------------
; вывести параметры командной строки
LL1:
 MOV EDI,CNT
 CMP NUM,EDI
 JE LL2
; номер параметра
 INC EDI
 MOV CNT, EDI
; получить параметр номером EDI
 LEA EBX,BUF
 CALL GETPAR
; получить длину параметра
 PUSH OFFSET BUF
 CALL LENSTR
; в конце - перевод строки
 MOV BYTE PTR [BUF+EBX],13
 MOV BYTE PTR [BUF+EBX+1],10
 MOV BYTE PTR [BUF+EBX+2],0
 ADD EBX,2
; вывод строки
 PUSH 0
 PUSH OFFSET LENS
 PUSH EBX
 PUSH OFFSET BUF
 PUSH HANDL
 CALL WriteConsoleA@20
 JMP LL1
LL2:
 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

; определить количество параметров (->EAX)
NUMPAR PROC
 CALL GetCommandLineA@0
 MOV ESI,EAX ; указатель на строку
 XOR ECX,ECX ; счетчик
 MOV EDX,1 ; признак
L1:
 CMP BYTE PTR [ESI],0
 JE L4
 CMP BYTE PTR [ESI],32
 JE L3
 ADD ECX,EDX ; номер параметра
 MOV EDX, 0
 JMP L2
L3:
 OR EDX, 1
L2:
 INC ESI
 JMP L1
L4:
 MOV EAX,ECX
 RET
NUMPAR ENDP

; получить параметр
; EBX - указывает на буфер, куда будет помещен параметр
; в буфер помещается строка с нулем на конце
; EDI - номер параметра
GETPAR PROC
 CALL GetCommandLineA@0
 MOV ESI, EAX ; указатель на строку
 XOR ECX, ECX ; счетчик
 MOV EDX,1 ; признак
L1:
 CMP BYTE PTR [ESI],0
 JE L4
 CMP BYTE PTR [ESI] ,32
 JE L3
 ADD ECX,EDX ; номер параметра
 MOV EDX, 0
 JMP L2
L3:
 OR EDX, 1
L2:
 CMP ECX, EDI
 JNE L5
 MOV AL, BYTE PTR [ESI]
 MOV BYTE PTR [EBX],AL
 INC EBX
L5:
 INC ESI
 JMP L1
L4:
 MOV BYTE PTR [EBX],0
 RET
GETPAR ENDP
_TEXT ENDS
END START

Рис. 2.2.6. Пример работы с параметрами командной строки.

Рекомендую читателю разобраться в алгоритме работы процедур NUMPAR и GETPAR.

Следует отметить, что для трансляции программы на Рис. 2.2.6 в TASM, кроме обычных, уже известных Вам изменений, для совпадающих меток следует в начале имени поставить "@@" - признак локальности, а в начале программы поставить директиву LOCALS. Транслятор MASM метки, стоящие в процедуре, считает локальными автоматически. Подробнее о локальных метках будет сказано в Главах 2.5 и 2.6.



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

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