Таймеры в консольном приложении
IV
В последнем разделе главы мы рассмотрим довольно редко освещаемый в литературе вопрос - таймеры в консольном приложении. Надо сказать, что мы несколько опережаем события и рассматриваем таймер в консольном приложении раньше, чем в приложении GUI (Graphic Universal Interface - так называются обычные оконные приложения).
Основным способом создания таймера является использование функции SetTimer. Позднее мы будем подробно о ней говорить. Таймер может быть установлен в двух режимах. Первый режим - это когда последний параметр равен нулю. В этом случае на текущее окно (его функцию) через равные промежутки времени, определяемые третьим параметром, будет приходить сообщение WM_TIMER. Во втором режиме последний параметр указывает на функцию, которая будет вызываться опять через равные промежутки времени. Однако для консольного приложения эта функция не подходит, так как сообщение WM_TIMER пересылается окну функцией DispatchMessage, которая используется в петле обработки сообщений. Но использование этой функции для консольных приложений проблематично.
Для консольных приложений следует использовать функцию timeSetEvent. Вот параметры этой функции:
Если функция завершилась удачно, то в EAX возвращается идентификатор таймера.
Сама вызываемая процедура получает также 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.