Assembler & Win32

Усов Александр

Пример 3. Оконное приложение

 

 

Файл dmenu.asm

Ideal

P586

Radix  16

Model  flat

struc  WndClassEx

       cbSize       dd     0

       style        dd     0

       lpfnWndProc  dd     0

       cbClsExtra   dd     0

       cbWndExtra   dd     0

       hInstance    dd     0

       hIcon        dd     0

       hCursor             dd     0

       hbrBackground dd     0

       lpszMenuName dd     0

       lpszClassName dd     0

       hIconSm             dd     0

ends   WndClassEx

struc  Point

       left         dd     0

       top          dd     0

       right        dd     0

       bottom       dd     0           

ends   Point

struc  msgStruc

       hwnd         dd     0

       message             dd     0

       wParam       dd     0

       lParam       dd     0

       time         dd     0

       pt           Point  <>

ends   msgStruc

MyMenu              = 0065

ID_OPEN                    = 9C41

ID_SAVE                    = 9C42

ID_EXIT                    = 9C43

CS_VREDRAW          = 0001

CS_HREDRAW          = 0002

IDI_APPLICATION            = 7F00

IDC_ARROW           = 7F00

COLOR_WINDOW        = 5

WS_EX_WINDOWEDGE    = 00000100

WS_EX_CLIENTEDGE    = 00000200

WS_EX_OVERLAPPEDWINDOW     = WS_EX_WINDOWEDGE OR WS_EX_CLIENTEDGE

WS_OVERLAPPED       = 00000000

WS_CAPTION          = 00C00000

WS_SYSMENU          = 00080000

WS_THICKFRAME       = 00040000

WS_MINIMIZEBOX             = 00020000

WS_MAXIMIZEBOX             = 00010000

WS_OVERLAPPEDWINDOW =      WS_OVERLAPPED     OR \

                           WS_CAPTION        OR \

                           WS_SYSMENU        OR \

                           WS_THICKFRAME     OR \

                           WS_MINIMIZEBOX    OR \

                           WS_MAXIMIZEBOX

CW_USEDEFAULT       = 80000000

SW_SHOW                    = 5

WM_COMMAND          = 0111

WM_DESTROY          = 0002

WM_CLOSE            = 0010

MB_OK               = 0

PROCTYPE     ptGetModuleHandle   stdcall      \

                    lpModuleName :dword

PROCTYPE     ptLoadIcon          stdcall      \

                    hInstance    :dword,      \

                    lpIconName   :dword

PROCTYPE     ptLoadCursor        stdcall      \

                    hInstance    :dword,      \

                    lpCursorName :dword

PROCTYPE     ptLoadMenu          stdcall      \

                    hInstance    :dword,      \

                    lpMenuName   :dword

PROCTYPE     ptRegisterClassEx   stdcall      \

                    lpwcx        :dword

PROCTYPE     ptCreateWindowEx    stdcall      \

                    dwExStyle    :dword,      \

                    lpClassName  :dword,      \

                    lpWindowName :dword,      \

                    dwStyle             :dword,      \

                    x            :dword, \

                    y            :dword,      \

                    nWidth       :dword,      \

                    nHeight             :dword,      \

                    hWndParent   :dword,      \

                    hMenu        :dword, \

                    hInstance    :dword,      \

                    lpParam             :dword

PROCTYPE     ptShowWindow        stdcall      \

                    hWnd         :dword,      \

                    nCmdShow     :dword

PROCTYPE     ptUpdateWindow             stdcall      \

                    hWnd         :dword

PROCTYPE     ptGetMessage        stdcall      \

                    pMsg         :dword,      \

                    hWnd         :dword,      \

                    wMsgFilterMin :dword,      \

                    wMsgFilterMax :dword

PROCTYPE     ptTranslateMessage  stdcall      \

                    lpMsg        :dword

PROCTYPE     ptDispatchMessage   stdcall      \

                    pmsg         :dword

PROCTYPE     ptSetMenu           stdcall      \

                    hWnd         :dword,      \

                    hMenu        :dword

PROCTYPE     ptPostQuitMessage   stdcall      \

                    nExitCode    :dword

PROCTYPE     ptDefWindowProc            stdcall      \

                    hWnd         :dword,      \

                    Msg          :dword,      \

                    wParam       :dword,      \

                    lParam       :dword

PROCTYPE     ptSendMessage       stdcall      \

                    hWnd         :dword,      \

                    Msg          :dword,      \

                    wParam       :dword,      \

                    lParam       :dword

PROCTYPE     ptMessageBox        stdcall      \

                    hWnd         :dword,      \

                    lpText       :dword,      \

                    lpCaption    :dword,      \

                    uType        :dword

PROCTYPE     ptExitProcess       stdcall      \

                    exitCode     :dword

extrn        GetModuleHandleA    :ptGetModuleHandle

extrn        LoadIconA           :ptLoadIcon

extrn        LoadCursorA         :ptLoadCursor

extrn        RegisterClassExA    :ptRegisterClassEx

extrn        LoadMenuA           :ptLoadMenu

extrn        CreateWindowExA            :ptCreateWindowEx

extrn        ShowWindow          :ptShowWindow

extrn        UpdateWindow        :ptUpdateWindow

extrn        GetMessageA         :ptGetMessage

extrn        TranslateMessage    :ptTranslateMessage

extrn        DispatchMessageA    :ptDispatchMessage

extrn        SetMenu                    :ptSetMenu

extrn        PostQuitMessage            :ptPostQuitMessage

extrn         DefWindowProcA             :ptDefWindowProc

extrn        SendMessageA        :ptSendMessage

extrn        MessageBoxA         :ptMessageBox

extrn        ExitProcess         :ptExitProcess

UDataSeg

hInst        dd           ?

hWnd         dd           ?

IFNDEF VER1

hMenu        dd           ?

ENDIF

DataSeg

msg          msgStruc     <>

classTitle   db     'Menu demo', 0

wndTitle     db     'Demo program', 0

msg_open_txt db     'You selected open', 0

msg_open_tlt db     'Open box', 0

msg_save_txt db     'You selected save', 0

msg_save_tlt db     'Save box', 0

CodeSeg

Start: call   GetModuleHandleA,   0      ; не обязательно, но желательно

       mov    [hInst],eax

       sub    esp,SIZE WndClassEx        ; отведём место в стеке под структуру

       mov    [(WndClassEx esp).cbSize],SIZE WndClassEx

       mov    [(WndClassEx esp).style],CS_HREDRAW or CS_VREDRAW

       mov    [(WndClassEx esp).lpfnWndProc],offset WndProc

       mov    [(WndClassEx esp).cbWndExtra],0

       mov    [(WndClassEx esp).cbClsExtra],0

       mov    [(WndClassEx esp).hInstance],eax

       call   LoadIconA,          0, IDI_APPLICATION

       mov    [(WndClassEx esp).hIcon],eax

       call   LoadCursorA,        0, IDC_ARROW

       mov    [(WndClassEx esp).hCursor],eax

       mov    [(WndClassEx esp).hbrBackground],COLOR_WINDOW

IFDEF  VER1

       mov    [(WndClassEx esp).lpszMenuName],MyMenu

ELSE

       mov    [(WndClassEx esp).lpszMenuName],0

ENDIF

       mov    [(WndClassEx esp).lpszClassName],offset classTitle

       mov    [(WndClassEx esp).hIconSm],0

       call   RegisterClassExA,   esp    ; зарегистрируем класс окна

       add    esp,SIZE WndClassEx        ; восстановим стек

                                         ; и создадим окно

IFNDEF VER2

       call   CreateWindowExA,    WS_EX_OVERLAPPEDWINDOW, \ extended window style

                                  offset classTitle, \ pointer to registered class name

                                  offset wndTitle,\ pointer to window name

                                  WS_OVERLAPPEDWINDOW,       \ window style

                                  CW_USEDEFAULT,      \ horizontal position of window

                                  CW_USEDEFAULT,      \ vertical position of window

                                  CW_USEDEFAULT,      \ window width

                                  CW_USEDEFAULT,      \ window height

                                  0,           \ handle to parent or owner window

                                  0,     \ handle to menu, or child-window identifier

                                  [hInst],     \ handle to application instance

                                  0            ; pointer to window-creation data

ELSE

       call   LoadMenu,           hInst, MyMenu

       mov    [hMenu],eax

       call   CreateWindowExA,    WS_EX_OVERLAPPEDWINDOW,    \ extended window style

                                  offset classTitle, \ pointer to registered class name

                                  offset wndTitle,    \ pointer to window name

                                  WS_OVERLAPPEDWINDOW,       \ window style

                                  CW_USEDEFAULT,      \ horizontal position of window

                                  CW_USEDEFAULT,      \ vertical position of window

                                  CW_USEDEFAULT,      \ window width

                                  CW_USEDEFAULT,      \ window height

                                  0,           \ handle to parent or owner window

                                  eax,   \ handle to menu, or child-window identifier

                                  [hInst],     \ handle to application instance

                                  0            ; pointer to window-creation data

ENDIF

       mov    [hWnd],eax

       call   ShowWindow,         eax, SW_SHOW        ; show window

       call   UpdateWindow,       [hWnd]              ; redraw window

IFDEF  VER3

       call   LoadMenuA,          [hInst], MyMenu

       mov    [hMenu],eax

       call   SetMenu,            [hWnd], eax

ENDIF

msg_loop:

       call   GetMessageA,        offset msg, 0, 0, 0

       or     ax,ax

       jz     exit

       call   TranslateMessage,   offset msg

       call   DispatchMessageA,   offset msg

       jmp    msg_loop

exit:  call   ExitProcess,        0

public stdcall      WndProc

proc   WndProc      stdcall

arg    @@hwnd:      dword, @@msg: dword, @@wPar:      dword, @@lPar:      dword

       mov    eax,[@@msg]

       cmp    eax,WM_COMMAND

       je     @@command

       cmp    eax,WM_DESTROY

       jne    @@default

       call   PostQuitMessage,    0

       xor    eax,eax

       jmp    @@ret

@@default:

       call   DefWindowProcA,     [@@hwnd], [@@msg], [@@wPar], [@@lPar]

@@ret: ret

@@command:

       mov    eax,[@@wPar]

       cmp    eax,ID_OPEN

       je     @@open

       cmp    eax,ID_SAVE

       je     @@save

       call   SendMessageA,       [@@hwnd], WM_CLOSE, 0, 0

       xor    eax,eax

       jmp    @@ret

@@open:      mov    eax, offset msg_open_txt

       mov    edx, offset msg_open_tlt

       jmp    @@mess

@@save:      mov    eax, offset msg_save_txt

       mov    edx, offset msg_save_tlt

@@mess:      call   MessageBoxA,        0, eax, edx, MB_OK

       xor    eax,eax

       jmp    @@ret

endp   WndProc

end    Start

 

Комментарии к программе

Здесь мне хотелось в первую очередь продемонстрировать использование прототипов функций API Win32. Конечно их (а также описание констант и структур из API Win32) следует вынести в отдельные подключаемые файлы, поскольку, скорее всего Вы будете использовать их и в других программах. Описание прототипов функций обеспечивает строгий контроль со стороны компилятора за количеством и типом параметров, передаваемых в функции. Это существенно облегчает жизнь программисту, позволяя избежать ошибок времени исполнения, тем более, что число параметров в некоторых функциях API Win32 весьма значительно.

Существо данной программы заключается в демонстрации вариантов работы с оконным меню. Программу можно откомпилировать в трёх вариантах (версиях), указывая компилятору ключи VER2 или VER3 (по умолчанию используется ключ VER1). В первом варианте программы меню определяется на уровне класса окна и все окна данного класса будут иметь аналогичное меню. Во втором варианте, меню определяется при создании окна, как параметр функции CreateWindowEx. Класс окна не имеет меню и в данном случае, каждое окно этого класса может иметь своё собственное меню. Наконец, в третьем варианте, меню загружается после создания окна. Данный вариант показывает, как можно связать меню с уже созданным окном.

Директивы условной компиляции позволяют включить все варианты в текст одной и той же программы. Подобная техника удобна не только для демонстрации, но и для отладки. Например, когда Вам требуется включить в программу новый фрагмент кода, то Вы можете применить данную технику, дабы не потерять функционирующий модуль. Ну, и конечно, применение директив условной компиляции – наиболее удобное средство тестирования различных решений (алгоритмов) на одном модуле.

Представляет определённый интерес использование стековых фреймов и заполнение структур в стеке посредством регистра указателя стека (esp). Именно это продемонстрировано при заполнении структуры WndClassEx. Выделение места в стеке (фрейма) делается простым перемещением esp:

       sub    esp,SIZE WndClassEx

Теперь мы можем обращаться к выделенной памяти используя всё тот же регистр указатель стека. При создании 16-битных приложений такой возможностью мы не обладали. Данный приём можно использовать внутри любой процедуры или даже произвольном месте программы. Накладные расходы на подобное выделение памяти минимальны, однако, следует учитывать, что размер стека ограничен и размещать большие объёмы данных в стеке вряд ли целесообразно. Для этих целей лучше использовать “кучи” (heap) или виртуальную память (virtual memory).

Остальная часть программы достаточно тривиальна и не требует каких-либо пояснений. Возможно более интересным покажется тема использования макроопределений.