Окна
Теперь, когда мы знаем, как просто выводится окно с предопределенным классом, возьмемся за вывод собственного окна — процедуры, на которой будут базироваться все последующие примеры, познакомимся с понятием сообщения. В 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, мы уже создали большую часть всех последующих программ, а когда мы дополним этот текст полноценным диалогом, обнаружится, что больше не нужно писать все эти громоздкие конструкции, достаточно просто копировать отдельные участки текста.
Содержание раздела