| [Назад] [Далее] | ![]() |
Теперь, когда мы знаем, как просто выводится окно с предопределенным классом, возьмемся за вывод собственного окна — процедуры, на которой будут базироваться все последующие примеры, познакомимся с понятием сообщения. В DOS основным средством передачи управления программам в различных ситуациях служат прерьшания. В Windows прерывания используются системой для своих нужд, а для приложений существует аналогичный механизм — механизм событий. Так, нажатие клавиши на клавиатуре, если эта клавиша не используется Windows, генерирует сообщение WM_KEYDOWN или WM_KEYUP, которое можно перехватить, добавив в цепь обработчиков события собственное при помощи SetWindowHookEx(). События затем преобразуются в сообщения, которые рассылаются функциям — обработчикам сообщений и которые можно прочитать из основной программы при помощи вызовов GetMessage() и PeekMessage().
Нам пока потребуется только обработка сообщения закрытия окна (WM_DESTROY и WM_QUIT), по которому программа будет завершаться.
; window.asm
; Графическое win32-приложение, демонстрирующее базовый вывод окна
;
include def32.inc
include kernel32.inc
include user32.inc
.386
.model flat
.data
class_name db "window class 1",0
window_name db "win32 assembly example",0
; структура, описывающая класс окна.
wc WNDCLASSEX <4*12,CS_HREDRAW or CS_VREDRAW,offset win_proc,0,0,?,?,?,
COLOR_WINDOW+1,0,offset class_name,0>
; здесь находятся следующие поля
; wc.cbSize = 4*12 - размер этой структуры
; wc.style - стиль окна (перерисовывать при изменении размера)
; wc.lpfnWndProc - обработчик событий окна (win_proc)
; wc.cbClsExtra - число дополнительных байтов после структуры (0)
; wc.cbWndExtra - число дополнительных байтов после окна (0)
; wc.hInstance - идентификатор нашего процесса (?)
; wc.hIcon - идентификатор иконки (?)
; wc.hCursor - идентификатор курсора (?)
; wc.hbrBackground - идентификатор кисти или цвет фона+1
; (COLOR_WINDOW+1)
; wc.lpszMenuName - ресурс с основным меню (в этом примере - 0)
; wc.lpszClassName - имя класса (строка class_name)
; wc.hIconSm - идентификатор маленькой иконки (только в windows 95,
; для NT должен быть 0)
.data?
msg_ MSG <?,?,?,?,?,?> ; а это - структура, в которой возвращается
; сообщение после GetMessage
.code
_start:
xor ebx,ebx ; в EBX будет 0 для команд push 0
; (короче в 2 раза)
; определим идентификатор нашей программы
push ebx
call GetModuleHandle
mov esi,eax ; и сохраним его в ESI
; заполним и зарегестрируем класс
mov dword ptr wc.hInstance,eax ; идентификатор предка
; выберем иконку
push IDI_APPLICATION ; стандартная иконка приложения
push ebx ; идентификатор модуля с иконкой
call LoadIcon
mov wc.hIcon,eax ; идентификатор иконки для нашего класса
; выберем форму курсора
push IDC_ARROW ; стандартная стрелка
push ebx ; идентификатор модуля с курсором
call LoadCursor
mov wc.hCursor,eax ; идентификатор курсора для нашего класса
push offset wc
call RegisterClassEx ; зарегистрируем класс
; создадим окно
mov ecx,CW_USEDEFAULT ; push ecx короче push N в пять раз
push ebx ; адрес структуры CREATESTRUCT (здесь NULL)
push esi ; идентификатор процесса, который будет получать
; сообщения от окна (то есть, наш)
push ebx ; идентификатор меню или окна-потомка
push ebx ; идентификатор окна-предка
push ecx ; высота (CW_USEDEFAULT - по умолчанию)
push ecx ; ширина (по умолчанию)
push ecx ; y-координата (по умолчанию)
push ecx ; x-координата (по умолчанию)
push WS_OVERLAPPEDWINDOW ; стиль окна
push offset window_name ; заголовок окна
push offset class_name ; любой зарегистрированный класс
push ebx ; дополнительный стиль
call CreateWindowEx ; создать окно (eax - идентификатор окна)
push eax ; идентификатор для UpdateWindow
push SW_SHOWNORMAL ; тип показа для для ShowWindow
push eax ; идентификатор для ShowWindow
; больше идентификатор окна нам не потребуется
call ShowWindow ; показать окно
call UpdateWindow ; и послать ему сообщение WM_PAINT
; основной цикл - проверка сообщений от окна и выход по WM_QUIT
mov edi,offset msg_ ; push edi короче push N в 5 раз
message_loop:
push ebx ; последнее сообщение
push ebx ; первое сообщение
push ebx ; идентификатор окна (0 - любое наше окно)
push edi ; адрес структуры MSG
call GetMessage ; получить сообщение от окна с ожиданием
; - не забывайте использовать PeekMessage
; если нужно в этом цикле что-то выполнять
test eax,eax ; если получено WM_QUIT
jz exit_msg_loop ; выйти
push edi ; иначе - преобразовать сообщения типа
call TranslateMessage ; WM_KEYUP в сообщения типа WM_CHAR
push edi
call DispatchMessage ; и послать их процедуре окна (иначе его просто
; нельзя будет закрыть)
jmp short message_loop ; продолжить цикл
exit_msg_loop:
; выход из программы
push ebx
call ExitProcess
; процедура win_proc
; вызывается окном каждый раз, когда окно получает какое-нибудь сообщение
; именно здесь будут происходить вся работа программы
;
; процедура не должна изменять регистры EBP,EDI,ESI и EBX !
;
win_proc proc
; так как мы получаем параметры в стеке, построим стековый кадр
push ebp
mov ebp,esp
; процедура типа WindowProc вызывается со следующими параметрами
wp_hWnd equ dword ptr [ebp+08h] ; идентификатор окна
wp_uMsg equ dword ptr [ebp+0Ch] ; номер сообщения
wp_wParam equ dword ptr [ebp+10h] ; первый параметр
wp_lParam equ dword ptr [ebp+14h] ; второй параметр
; если мы получили сообщение WM_DESTROY (оно означает что окно уже удалили
; с экрана, нажав alt-F4 или кнопку в верхнем правом углу)
; то пошлем основной программе сообщение WM_QUIT
cmp wp_uMsg,WM_DESTROY
jne not_wm_destroy
push 0 ; код выхода
call PostQuitMessage ; послать WM_QUIT
jmp short end_wm_check ; и выйти из процедуры
not_wm_destroy:
; если мы получили другое сообщение - вызовем его обработчик по умолчанию
leave ; восстановим ebp
jmp DefWindowProc ; и вызовем DefWindowProc с нашими параметрами
; и адресом возврата в стеке
end_wm_check:
leave ; восстановим ebp
ret 16 ; и вернемся сами, очистив стек от параметров
win_proc endp
end _start
Необходимые добавления в файл def32.inc:
; из winuser.h
IDI_APPLICATION equ 32512
WM_DESTROY equ 2
CS_HREDRAW equ 2
CS_VREDRAW equ 1
CW_USEDEFAULT equ 80000000h
WS_OVERLAPPEDWINDOW equ 0CF0000h
IDC_ARROW equ 32512
SW_SHOWNORMAL equ 1
COLOR_WINDOW equ 5
WNDCLASSEX struc
cbSize dd ?
style dd ?
lpfnWndProc dd ?
cbClsExtra dd ?
cbWndExtra dd ?
hInstance dd ?
hIcon dd ?
hCursor dd ?
hbrBackground dd ?
lpszMenuName dd ?
lpszClassName dd ?
hIconSm dd ?
WNDCLASSEX ends
MSG struc
hwnd dd ?
message dd ?
wParam dd ?
lParam dd ?
time dd ?
pt dd ?
MSG ends
Добавления в файл user32.inc:
между ifdef _TASM_ и else:
extrn DispatchMessageA:near
extrn TranslateMessage:near
extrn GetMessageA:near
extrn LoadIconA:near
extrn UpdateWindow:near
extrn ShowWindow:near
extrn CreateWindowExA:near
extrn DefWindowProcA:near
extrn PostQuitMessage:near
extrn RegisterClassExA:near
extrn LoadCursorA:near
; присваивания для облегчения читаемости кода
DispatchMessage equ DispatchMessageA
GetMessage equ GetMessageA
LoadIcon equ LoadIconA
CreateWindowEx equ CreateWindowExA
DefWindowProc equ DefWindowProcA
RegisterClassEx equ RegisterClassExA
LoadCursor equ LoadCursorA
и между else и endif:
extrn __imp__DispatchMessageA@4:dword
extrn __imp__TranslateMessage@4:dword
extrn __imp__GetMessageA@16:dword
extrn __imp__LoadIconA@8:dword
extrn __imp__UpdateWindow@4:dword
extrn __imp__ShowWindow@8:dword
extrn __imp__CreateWindowExA@48:dword
extrn __imp__DefWindowProcA@16:dword
extrn __imp__PostQuitMessage@4:dword
extrn __imp__RegisterClassExA@4:dword
extrn __imp__LoadCursorA@8:dword
; присваивания для облегчения читаемости кода
DispatchMessage equ __imp__DispatchMessageA@4
TranslateMessage equ __imp__TranslateMessage@4
GetMessage equ __imp__GetMessageA@16
LoadIcon equ __imp__LoadIconA@8
UpdateWindow equ __imp__UpdateWindow@4
ShowWindow equ __imp__ShowWindow@8
CreateWindowEx equ __imp__CreateWindowExA@48
DefWindowProc equ __imp__DefWindowProcA@16
PostQuitMessage equ __imp__PostQuitMessage@4
RegisterClassEx equ __imp__RegisterClassExA@4
LoadCursor equ __imp__LoadCursorA@8
а в файл kernel32.inc между ifdef _TASM_ и else:
extrn GetModuleHandleA:near
GetModuleHandle equ GetModuleHandleA
и между else и endif:
extrn __imp__GetModuleHandleA@4:dword
GetModuleHandle equ __imp__GetModuleHandleA@4
В начале главы говорилось, что программировать под Windows просто, а в то же время текст обычной программы вывода пустого окна на экран уже занимает больше места, чем, например, программа проигрывания wav-файла из главы 5.10.8. Где же обещанная простота? Так вот, оказывается, что, написав window.asm, мы уже создали большую часть всех последующих программ, а когда мы дополним этот текст полноценным диалогом, обнаружится, что больше не нужно писать все эти громоздкие конструкции, достаточно просто копировать отдельные участки текста.