III
После разбора программы на Рис. 1.2.2 возникает вопрос по поводу ее
реализации на ассемблере TASM. В действительности здесь требуются минимальные
изменения: вместо библиотек user32.lib
и kernel32.lib
надо подключить библиотеку import32.lib
, удалить во всех именах
библиотечных процедур @N
и далее выполнить команды tasm32 /ml
prog.asm
и tlink32 -aa prog.obj
.
.386P ; плоская модель MODEL FLAT, stdcall ; константы ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при создании окна WM_CREATE equ 1 ; сообщение при щелчке левой кнопкой мыши в области окна WM_LBUTTONDOWN equ 201h ; сообщение при щелчке правой кнопкой мыши в области окна WM_RBUTTONDOWN equ 204h ; свойства окна CS_VREDRAW equ 1h CS_HREDRAW equ 2h CS_GLOBALCLASS equ 4000h WS_OVERLAPPEDWINDOW equ 000CF0000H style equ CS_HREDRAW+CS_VREDRAW+CS_GLOBALCLASS ; идентификатор стандартной иконки IDI_APPLICATION equ 32512 ; идентификатор курсора IDC_CROSS equ 32515 ; режим показа окна — нормальный SW_SHOWNORMAL equ 1 ; прототипы внешних процедур EXTERN MessageBoxA:NEAR EXTERN CreateWindowExA:NEAR EXTERN DefWindowProcA:NEAR EXTERN DispatchMessageA:NEAR EXTERN ExitProcess:NEAR EXTERN GetMessageA:NEAR EXTERN GetModuleHandleA:NEAR EXTERN LoadCursorA:NEAR EXTERN LoadIconA:NEAR EXTERN PostQuitMessage:NEAR EXTERN RegisterClassA:NEAR EXTERN ShowWindow:NEAR EXTERN TranslateMessage:NEAR EXTERN UpdateWindow:NEAR ; директивы компоновщику для подключения библиотек includelib c:\tasm32\lib\import32.lib ;-------------------------------------------------- ; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? ; идентификатор окна, ; получающего сообщение MSMESSAGE DD ? ; идентификатор сообщения MSWPARAM DD ? ; доп. информация о сообщении MSLPARAM DD ? ; доп. информация о сообщении MSTIME DD ? ; время посылки сообщения MSPT DD ? ; положение курсора, во время посылки сообщения MSGSTRUCT ENDS ; --------- WNDCLASS STRUC CLSSTYLE DD ? ; стиль окна CLWNDPROC DD ? ; указатель на процедуру окна CLSCEXTRA DD ? ; информация о доп. байтах для ; данной структуры CLWNDEXTRA DD ? ; информация о доп. байтах для окна CLSHINSTANCE DD ? ; дескриптор приложения CLSHICON DD ? ; идентификатор иконы окна CLSHCURSOR DD ? ; идентификатор курсора окна CLBKGROUND DD ? ; идентификатор кисти окна CLMENUNAME DD ? ; имя-идентификатор меню CLNAME DD ? ; специфицирует имя класса окон WNDCLASS ENDS ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> HINST DD 0 ; здесь хранится дескриптор приложения TITLENAME DB 'Простой пример 32-битного приложения',0 CLASSNAME DB 'CLASS32',0 CAP DB 'Сообщение',0 MES1 DB 'Вы нажали левую кнопку мыши',0 MES2 DB 'Выход из программы. Пока!',0 _DATA ENDS ; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA MOV [HINST], EAX REG_CLASS: ; заполнить структуру окна стиль MOV [WC.CLSSTYLE], style ; процедура обработки сообщений MOV [WC.CLWNDPROC], OFFSET WNDPROC MOV [WC.CLSCSEXTRA], 0 MOV [WC.CLWNDEXTRA], 0 MOV EAX, [HINST] MOV [WC.CLSHINSTANCE], EAX ; ---------- иконка окна PUSH IDI_APPLICATION PUSH 0 CALL LoadIconA MOV [WC.CLSHICON], EAX ;----------- курсор окна PUSH IDC_CROSS PUSH 0 CALL LoadCursorA MOV [WC.CLSHCURSOR], EAX ; ---------- MOV [WC.CLBKGROUND], 17 ; цвет окна MOV DWORD PTR [WC.CLMENUNAME], 0 MOV DWORD PTR [WC.CLNAME], OFFSET CLASSNAME PUSH OFFSET WC CALL RegisterClassA ; создать окно зарегистрированного класса PUSH 0 PUSH [HINST] PUSH 0 PUSH 0 PUSH 400 ; DY - высота окна PUSH 400 ; DX - ширина окна PUSH 100 ; Y - координата левого верхнего угла PUSH 100 ; X - координата левого верхнего угла PUSH WS_OVERLAPPEDWINDOW PUSH OFFSET TITLENAME ; имя окна PUSH OFFSET CLASSNAME ; имя класса PUSH 0 CALL CreateWindowExA ; проверка на ошибку CMP EAX, 0 JZ _ERR MOV [NEWHWND], EAX PUSH SW_SHOWNORMAL PUSH [NEWHWND] CALL ShowWindow ; показать созданное окно PUSH [NEWHWND] CALL UpdateWindow ; команда перерисовать видимую ; часть окна, сообщение WM_PAINT ; петля обработки сообщений MSG_LOOP: PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET MSG CALL GetMessageA CMP EAX, 0 JE END_LOOP PUSH OFFSET MSG CALL TranslateMessage PUSH OFFSET MSG CALL DispatchMessageA JMP MSG_LOOP END_LOOP: ; выход из программы (закрыть процесс) PUSH [MSG.MSWPARAM] CALL ExitProcess _ERR: JMP END_LOOP ;-------------------------------------------------- ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] LPARAM ; [EBP+10H] WAPARAM ; [EBP+0СН] MES ; [EBP+8] HWND WNDPROC PROC PUSH EBP MOV EBP, ESP PUSH EBX PUSH ESI PUSH EDI CMP DWORD PTR [EBP+0CH], WM_DESTROY JE WMDESTROY CMP DWORD PTR [EBP+0CH], WM_CREATE JE WMCREATE CMP DWORD PTR [EBP+0CH], WM_LBUTTONDOWN ; левая кнопка JE LBUTTON CMP DWORD PTR [EBP+0CH], WM_RBUTTONDOWN ; правая кнопка JE RBUTTON JMP DEFWNDPROC ; нажатие правой кнопки приводит к закрытию окна RBUTTON: JMP WMDESTROY ; нажатие левой кнопки мыши LBUTTON: ; выводим сообщение PUSH 0 ; МВ_ОК PUSH OFFSET CAP PUSH OFFSET MES1 PUSH DWORD PTR [EBP+08H] CALL MessageBoxA MOV EAX, 0 JMP FINISH WMCREATE: MOV EAX, 0 JMP FINISH DEFWNDPROC: PUSH DWORD PTR [EBP+14H] PUSH DWORD PTR [EBP+10H] PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+08H] CALL DefWindowProcA JMP FINISH WMDESTROY: PUSH 0 ; MB_OK PUSH OFFSET CAP PUSH OFFSET MES2 PUSH DWORD PTR [EBP+08H] ; дескриптор окна CALL MessageBoxA PUSH 0 CALL PostQuitMessage ; сообщение WM_QUIT MOV EAX, 0 FINISH: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START
Рис. 1.2.3. Простой пример программы для Windows (TASM32).
Кстати заметьте, что исполняемый модуль, транслируемый в TASM32, всегда несколько длиннее аналогичного модуля, транслируемого с помощью пакета MASM32.
Обратите внимание на то, как задаются свойства окон. Описание этих свойств
можно найти в программе помощи для функции CreateWindow
.
В принципе, подбор необходимых сочетаний свойств окна — довольно кропотливое занятие. Это одна из причин, из-за которой понятие ресурса стало неотъемлемой частью загружаемого модуля. С понятием ресурса мы познакомимся в последующих главах.
Приведенные примеры показывают, что процедура окна должна правильно реагировать на пришедшие сообщения. В дальнейшем Вы узнаете, что и сама процедура может посылать сообщения приложению, другим окнам, а также своему окну21.
Часть API-функций получила суффикс "А". Дело в том, что функции имеют два
прототипа: с суффиксом "А
" - поддерживают ANSI, а с суффиксом
"W
" - Unicode.
21 Собственно PostQuitMessage
есть
пример посылки сообщения своему приложению.
IV
Здесь мне хотелось бы рассмотреть подробнее вопрос о передачи параметров через стек.
Это не единственный способ передачи параметров, но именно через стек передаются параметры API-функциям, поэтому на это необходимо обратить внимание.
Состояния стека до и после вызова процедуры приводится на Рис. 1.2.4.
Рис. 1.2.4 демонстрирует стандартный вход в процедуру, практикующийся в таких языках высокого уровня, как Паскаль и Си.
При входе в процедуру выполняется стандартная последовательность команд:
PUSH EBP MOV EBP, ESP SUB ESP, N ; N - количество байт для локальных переменных.
Адрес первого параметра определяется как [EBP+8Н]
, что мы уже
неоднократно использовали. Адрес первой локальной переменной, если она
зарезервирована, определяется как [EBP-4]
(имеется в виду
переменная типа DWORD
). На ассемблере не очень удобно использовать
локальные переменные, и мы не будем резервировать для них место (смотрите,
однако, Главу 2.5).
В конце процедуры идут команды:
MOV ESP, EBP POP EBP RET М
Здесь M
- объем, взятый у стека для передачи параметров.
Такого же результата можно добиться, используя команду ENTER N,0 (PUSH
EBP\MOV EBP,ESP\SUB ESP)
в начале процедуры и LEAVE (MOV
ESP,EBP\POP EBP)
в конце процедуры.
Эти команды появились еще у 286-ого процессора и дали возможность несколько оптимизировать транслируемый код программы, особенно в тех случаях, когда речь идет о больших по объему модулях, создаваемых на языке высокого уровня.
Хотелось бы остановиться еще на одном вопросе, связанным со структурой процедуры и ее вызова.
Существуют два основных подхода (см. [1]). Условно первый подход можно назвать Си-подходом, а второй — Паскаль-подходом.
Первый подход предполагает, что процедура "не знает", сколько параметров
находится в стеке. Естественно, в этом случае освобождение стека от параметров
должно происходить после команды вызова процедуры, например, с помощью команды
POP
или команды ADD ESP,N
(N
- количество
байт в параметрах).
Второй подход основан на том, что количество параметров фиксировано, а стек
можно освободить в самой процедуре. Это достигается выполнением команды
RET N
(N
- количество байт в параметрах). Как Вы уже,
наверное, догадались, вызов функций API осуществяется по второй схеме. Впрочем,
есть и исключения, о которых вы узнаете несколько позже (см. Гл. 2.1).