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

7.3.3. Меню

Меню — это один из краеугольных камней идеологии Windows. Похожие друг на друга меню позволяют пользоваться совершенно незнакомыми программами, не читая инструкций, и знакомиться с их возможностями, просто посмотрев содержание различных пунктов меню. Давайте добавим меню и в нашу программу window.asm.

Первое, что мы должны будем сделать, — это само меню. Меню, так же как и иконки, диалоги и другая информация (вплоть до версии программы), записывают в файлы ресурсов. Файл ресурсов имеет расширение *.RC для текстового файла или *.RES для бинарного файла, скомпилированного специальным компилятором ресурсов (RC, BRCC32 или WRC). И те, и другие файлы ресурсов можно редактировать специальными программами, входящими в дистрибутивы C/C++ или других средств разработки для Windows, но мы не будем делать слишком сложное меню и напишем RC-файл вручную, например так:

// winmenu.rc
// файл ресурсов для программы winmenu.asm
//
#define ZZZ_TEST 0
#define ZZZ_OPEN 1
#define ZZZ_SAVE 2
#define ZZZ_EXIT 3

ZZZ_Menu MENU {
    POPUP "&File;" {
        MENUITEM "&Open;",ZZZ_OPEN
        MENUITEM "&Save;", ZZZ_SAVE
        MENUITEM SEPARATOR
        MENUITEM "E&xit;",ZZZ_EXIT
    }
    MENUITEM "&Edit;",ZZZ_TEST
}

Чтобы добавить этот файл в программу, его надо скомпилировать и указать имя скомпилированного *.RES-файла для компоновщика:


MASM:

ml /c /coff /Cp winmenu.asm
rc /r winmenu.rc
link winmenu.obj winmenu.res /subsystem:windows

TASM:

tasm /m /ml /D_TASM_ winmenu.asm
brcc32 winmenu.rc
tlink32 /Tpe /aa /c /x winmenu.obj,,,,,winmenu.res

WASM:

wasm winmenu.rc
wrc /r /bt=nt winmenu.rc
wlink file winmenu.obj res winmenu.res form windows nt

А теперь сам текст программы. Чтобы показать, как мало надо внести изменений в программу window.asm, комментарии для всех строк, перенесенных оттуда без изменений заменены, на символ «*».

; winmenu.asm
; Графическое win32-приложение, демонстрирующее работу с меню
; звездочками отмечены строки, скопированные из файла window.asm
;

ZZZ_TEST   equ     0                ; сообщения от нашего меню
ZZZ_OPEN   equ     1                ; должны совпадать с определениями из winmenu.rc
ZZZ_SAVE   equ     2                ; кроме того в нашем примере их номера важны
ZZZ_EXIT   equ     3                ; потому что они используются как индекс для
                                    ; таблицы переходов к обработчикам

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  ;*
menu_name          db    "ZZZ_Menu",0                ; имя меню в файле ресурсов
test_msg           db    "You selected menu item TEST",0   ; строки для
open_msg           db    "You selected menu item OPEN",0   ; демонстрации работы
save_msg           db    "You selected menu item SAVE",0   ; меню
wc    WNDCLASSEX   <4*12,CS_HREDRAW or CS_VREDRAW,offset win_proc,0,0,?,?,?,\
                         COLOR_WINDOW+1,0,offset class_name,0> ;*
        .data?                            ;*
msg_    MSG        <?,?,?,?,?,?>          ;*
        .code                             ;*
_start:                                   ;*
        xor        ebx,ebx                ;*
        push       ebx                    ;*
        call       GetModuleHandle        ;*
        mov        esi,eax                ;*
        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        ;*
        push       offset menu_name       ; имя меню
        push       esi                    ; наш идентификатор
        call       LoadMenu               ; загрузим меню из ресурсов
        mov        ecx,CW_USEDEFAULT      ;*
        push       ebx                    ;*
        push       esi                    ;*
        push       eax                    ; идентификатор меню или окна-потомка
        push       ebx                    ;*
        push       ecx                    ;*
        push       ecx                    ;*
        push       ecx                    ;*
        push       ecx                    ;*
        push       WS_OVERLAPPEDWINDOW    ;*
        push       offset window_name     ;*
        push       offset class_name      ;*
        push       ebx                    ;*
        call       CreateWindowEx         ;*
        push       eax                    ;*
        push       SW_SHOWNORMAL          ;*
        push       eax                    ;*
        call       ShowWindow             ;*
        call       UpdateWindow           ;*
        mov        edi,offset msg_        ;*
message_loop:                             ;*
        push       ebx                    ;*
        push       ebx                    ;*
        push       ebx                    ;*
        push       edi                    ;*
        call       GetMessage             ;*
        test       eax,eax                ;*
        jz         exit_msg_loop          ;*
        push       edi                    ;*
        call       TranslateMessage       ;*
        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                ;*
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]    ;*
        cmp        wp_uMsg,WM_DESTROY     ;*
        jne        not_wm_destroy         ;*
        push       0                      ;*
        call       PostQuitMessage        ;*
        jmp        short end_wm_check     ;*
not_wm_destroy:                           ;*
        cmp        wp_uMsg,WM_COMMAND     ; если мы получили WM_COMMAND
        jne        not_wm_command         ; это от нашего меню
        mov        eax,wp_wParam          ; и в wParam лежит наше подсообщение
        jmp        dword ptr menu_handlers[eax*4]   ; косвенный переход
        ; (в 32-битном режиме можно делать переход по любому регистру)

menu_handlers   dd      offset menu_test,offset menu_open
                dd      offset menu_save,offset menu_exit

; обработчики событий test, open и save выводят MessageBox
; обработчик exit выходит из программы

menu_test:
        mov        eax,offset test_msg    ; сообщение для MessageBox
        jmp        short show_msg
menu_open:
        mov        eax,offset open_msg    ; сообщене для MessageBox
        jmp        short show_msg
menu_save:
        mov        eax,offset save_msg    ; сообщение для MessageBox
show_msg:
        push       MB_OK                  ; стиль для MessageBox
        push       offset menu_name       ; заголовок
        push       eax                    ; сообщение
        push       wp_hWnd                ; идентификатор окна-предка
        call       MessageBox             ; вызов функции
        jmp        short end_wm_check     ; выход из win_proc
menu_exit:                                ; если выбрали пункт EXIT
        push       wp_hWnd
        call       DestroyWindow          ; уничтожим наше окно
end_wm_check: 
        leave                             ;*
        xor        eax,eax      ; вернем 0 как результат работы процедуры
        ret        16                     ;*
not_wm_command:                 ; not_wm_command, чтобы избавиться от лишнего jmp
        leave                             ;*
        jmp        DefWindowProc          ;*
win_proc endp                             ;*
        end        _start                 ;*

Итого: из 120 строк программы новыми оказались всего 36, в то время как программа, с точки зрения пользователя, стала намного сложнее. Так и выглядит все программирование под Windows на ассемблере — берется одна написанная раз и навсегда шаблонная программа, модифицируются ресурсы и пишутся обработчики для различных событий меню и диалогов. Фактически все программирование оказывается сосредоточенным именно в этих процедурах-обработчиках.

Добавления к включаемым файлам в этом примере тоже оказываются незначительными по сравнению с window.asm.

В user32.inc между ifdef _TASM_ и else:

                   extrn    LoadMenuA:near
                   extrn    DestroyWindow:near
LoadMenu           equ      LoadMenuA

и между else и endif:

                   extrn    __imp__LoadMenuA@8:dword
                   extrn    __imp__DestroyWindow@4:dword
LoadMenu           equ      __imp__LoadMenuA@8
DestroyWindow      equ      __imp__DestroyWindow@4

и в def32.inc:

; из winuser.h
WM_COMMAND         equ      111h
MB_OK              equ      0

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