[Назад] [Далее]

7.3.2. Окна

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


п»ї
"target=_blank><\/a>") //-->