Assembler & Win32
Страница 3. Оконное приложение


 

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

Файл dmenu.asm
Ideal
P586
Radix16
Modelflat

strucWndClassEx
cbSizedd0
styledd0
lpfnWndProcdd0
cbClsExtradd0
cbWndExtradd0
hInstancedd0
hIcondd0
hCursordd0
hbrBackgrounddd0
lpszMenuNamedd0
lpszClassNamedd0
hIconSmdd0
endsWndClassEx

strucPoint
leftdd0
topdd0
rightdd0
bottomdd0
endsPoint

strucmsgStruc
hwnddd0
messagedd0
wParamdd0
lParamdd0
timedd0
ptPoint<>
endsmsgStruc

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

PROCTYPEptGetModuleHandlestdcall\
lpModuleName:dword

PROCTYPEptLoadIconstdcall\
hInstance:dword,\
lpIconName:dword

PROCTYPEptLoadCursorstdcall\
hInstance:dword,\
lpCursorName:dword

PROCTYPEptLoadMenustdcall\
hInstance:dword,\
lpMenuName:dword

PROCTYPEptRegisterClassExstdcall\
lpwcx:dword

PROCTYPEptCreateWindowExstdcall\
dwExStyle:dword,\
lpClassName:dword,\
lpWindowName:dword,\
dwStyle:dword,\
x:dword, \
y:dword,\
nWidth:dword,\
nHeight:dword,\
hWndParent:dword,\
hMenu:dword, \
hInstance:dword,\
lpParam:dword

PROCTYPEptShowWindowstdcall\
hWnd:dword,\
nCmdShow:dword

PROCTYPEptUpdateWindowstdcall\
hWnd:dword

PROCTYPEptGetMessagestdcall\
pMsg:dword,\
hWnd:dword,\
wMsgFilterMin:dword,\
wMsgFilterMax:dword

PROCTYPEptTranslateMessagestdcall\
lpMsg:dword

PROCTYPEptDispatchMessagestdcall\
pmsg:dword

PROCTYPEptSetMenustdcall\
hWnd:dword,\
hMenu:dword

PROCTYPEptPostQuitMessagestdcall\
nExitCode:dword

PROCTYPEptDefWindowProcstdcall\
hWnd:dword,\
Msg:dword,\
wParam:dword,\
lParam:dword

PROCTYPEptSendMessagestdcall\
hWnd:dword,\
Msg:dword,\
wParam:dword,\
lParam:dword

PROCTYPEptMessageBoxstdcall\
hWnd:dword,\
lpText:dword,\
lpCaption:dword,\
uType:dword

PROCTYPEptExitProcessstdcall\
exitCode:dword

extrnGetModuleHandleA:ptGetModuleHandle
extrnLoadIconA:ptLoadIcon
extrnLoadCursorA:ptLoadCursor
extrnRegisterClassExA:ptRegisterClassEx
extrnLoadMenuA:ptLoadMenu
extrnCreateWindowExA:ptCreateWindowEx
extrnShowWindow:ptShowWindow
extrnUpdateWindow:ptUpdateWindow
extrnGetMessageA:ptGetMessage
extrnTranslateMessage:ptTranslateMessage
extrnDispatchMessageA:ptDispatchMessage
extrnSetMenu:ptSetMenu
extrnPostQuitMessage:ptPostQuitMessage
extrnDefWindowProcA:ptDefWindowProc
extrnSendMessageA:ptSendMessage
extrnMessageBoxA:ptMessageBox
extrnExitProcess:ptExitProcess

UDataSeg
hInstdd?
hWnddd?

IFNDEFVER1
hMenudd?
ENDIF


DataSeg
msgmsgStruc<>
classTitledb'Menu demo', 0
wndTitledb'Demo program', 0
msg_open_txtdb'You selected open', 0
msg_open_tltdb'Open box', 0
msg_save_txtdb'You selected save', 0
msg_save_tltdb'Save box', 0

CodeSeg
Start:callGetModuleHandleA,0; не обязательно, но желательно
mov[hInst],eax

subesp,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
callLoadIconA,0, IDI_APPLICATION
mov[(WndClassEx esp).hIcon],eax
callLoadCursorA,0, IDC_ARROW
mov[(WndClassEx esp).hCursor],eax
mov[(WndClassEx esp).hbrBackground],COLOR_WINDOW
IFDEFVER1
mov[(WndClassEx esp).lpszMenuName],MyMenu
ELSE
mov[(WndClassEx esp).lpszMenuName],0
ENDIF
mov[(WndClassEx esp).lpszClassName],offset classTitle
mov[(WndClassEx esp).hIconSm],0
callRegisterClassExA,esp; зарегистрируем класс окна

addesp,SIZE WndClassEx; восстановим стек
; и создадим окно
IFNDEFVER2
callCreateWindowExA,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
callLoadMenu,hInst, MyMenu
mov[hMenu],eax
callCreateWindowExA,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
callShowWindow,eax, SW_SHOW; show window
callUpdateWindow,[hWnd]; redraw window

IFDEFVER3
callLoadMenuA,[hInst], MyMenu
mov[hMenu],eax
callSetMenu,[hWnd], eax
ENDIF

msg_loop:
callGetMessageA,offset msg, 0, 0, 0
orax,ax
jzexit
callTranslateMessage,offset msg
callDispatchMessageA,offset msg
jmpmsg_loop
exit:callExitProcess,0

publicstdcallWndProc
procWndProcstdcall
arg@@hwnd:dword,@@msg:dword,@@wPar:dword,@@lPar:dword
moveax,[@@msg]
cmpeax,WM_COMMAND
je@@command
cmpeax,WM_DESTROY
jne@@default
callPostQuitMessage,0
xoreax,eax
jmp@@ret
@@default:
callDefWindowProcA,[@@hwnd], [@@msg], [@@wPar], [@@lPar]
@@ret:ret
@@command:
moveax,[@@wPar]
cmpeax,ID_OPEN
je@@open
cmpeax,ID_SAVE
je@@save
callSendMessageA,[@@hwnd], WM_CLOSE, 0, 0
xoreax,eax
jmp@@ret
@@open:moveax, offset msg_open_txt
movedx, offset msg_open_tlt
jmp@@mess
@@save:moveax, offset msg_save_txt
movedx, offset msg_save_tlt
@@mess:callMessageBoxA,0, eax, edx, MB_OK
xoreax,eax
jmp@@ret
endpWndProc
endStart

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

 

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

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

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

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

subesp,SIZE WndClassEx

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

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

 
« Предыдущая статья