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).
Остальная часть программы достаточно тривиальна и не требует каких-либо пояснений. Возможно более интересным покажется тема использования макроопределений.