Win32asm обучение

  d8ef8794     

Окна в windows


     Win32Asm Tutorial     
 назад13- Окна в windows вперед

12.0 - Окна в windows

В этом уроке мы создадим программу с окном.

12.1 - Окна

Вы наверняка догадываетесь, почему windows так назван. В windows, есть два типа программ: GUI-приложения и консольные приложения. Консольные программы похожи на DOS-программы, они выполняются в поле подобном DOS. Большинство программ, которые вы используете это GUI (graphical user interface) приложения. У них есть графический интерфейс, для взаимодействия с пользователем. Это сделано, созданием окон. Почти все, что вы видите в windows, это окна. Сначала вы создаете родительское окно, а затем его дочерние окна (контролы) такие, как окна редактирования, статические контролы, кнопки и т.д.

12.2 - Классы окон

Каждое окно имеет имя класса. Для вашего родительского окна, вы определяете ваш собственный класс. Для контролов, вы можете использовать стандартные классы окон (такие как 'EDIT', 'STATIC', 'BUTTON').

12.3 - Структуры

Класс окна в вашей программе регистрируется с помощью функции 'RegisterClassEx' (RegisterClassEx это расширенная версия RegisterClass. RegisterClass используется редко). Объявление этой функции:

ATOM RegisterClassEx(

CONST WNDCLASSEX *lpwcx // адрес структуры с данными класса
);

lpwcx: Указатель на структуру WNDCLASSEX. Вы должны заполнить структуру соответствующими классу аттрибутами перед передачей ее функции.

Единственный параметр это указатель на структуру. Сначала некоторые основы относительно структур:

Структура это набор переменных (данных). Структура определяется директивой STRUCT:

SOMESTRUCTURE STRUCT
dword1 dd ?
dword2 dd ?
some_word dw ?
abyte db ?
anotherbyte db ?
SOMESTRUCTURE ENDS

(имя структуры не должно содержать прописных букв)

Вы также можете объявить ваши переменные в секции неинициализированных данных, со знаком вопроса. Теперь вы можете создать структуру из объявления:

Инициализированная

Initializedstructure SOMESTRUCTURE <100,200,10,'A',90h>


Неинициализированная
UnInitializedstructure SOMESTRUCTURE <>
В первом примере, новая структура создана (Initializedstructure содержит ее смещение), и каждый элемент данных структуры заполнен начальным значением. Второй пример просто говорит masm'у зарезервировать память для структуры, и каждый элемент данных структуры установить в 0. После создания структуры вы можете обращаться к любому ее элементу:
mov eax, Initializedstructure.some_word
; теперь eax будет содержать 10
inc UnInitializedstructure.dword1
; переменная dword1 структуры увеличена на 1
Таблица показывает, как эта структура была бы сохранена в памяти.

Расположение в памятиСодержимое
offset of Initializedstructure100 (dword, 4 байта)
offset of Initializedstructure + 4200 (dword, 4 байта)
offset of Initializedstructure + 810 (word, 2 байта)
offset of Initializedstructure + 1065 or 'A' (1 байт)
offset of Initializedstructure + 1190h (1 байт)

12.3 - WNDCLASSEX
Достаточно о структурах, давайте продолжать RegisterClassEx. В win32 справочнике программиста вы можете найти описание структуры WNDCLASSEX.
typedef struct _WNDCLASSEX { // wc
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX;
Анализ:
cbSizeРазмер структуры WNDCLASSEX в байтах. Вы можете получить этот размер с помощью оператора SIZEOF:
mov ws.cbSize, SIZEOF WNDCLASSEX
styleСтиль окон, создаваемых из это класса.
lpfnWndProcУказатель на процедуру окна (об этом я расскажу ниже)
cbClsExtraКоличество дополнительных байтов, которые нужно зарезервировать (они будут следовать за самой структурой).
cbWndExtraКоличество дополнительных батов, которые нужно зарезервировать (они будут следовать за window instance).
hInstanceХэндл вашей программы. Вы можете получить это хэндл функцией GetModuleHandle.
hIconХэндл иконки. Получите его функцией LoadIcon.
hCursorХэндл курсора. Получите его функцией LoadCursor.
hbrBackgroundХэндл кисти для закрашивания фона, или один из стандартных, таких как COLOR_WINDOW, COLOR_BTNFACE , COLOR_BACKGROUND.
lpszMenuNameУказатель на строку с нулевым символом в конце, которая определяет имя ресурса меню класса. Это также может быть ID ресурса.
lpszClassNameУказатель на строку с нулевым символом в конце, которая определяет имя класса для окон.
hIconSmХэнд маленькой иконки.

Создайте новую подпапку firstwindow в папке win32 и создайте новый файл window.asm в этой папке со следующим содержанием:
.486
.model flat, stdcall
option casemap:none

includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\gdi32.inc
Затем создайте .bat-файл с именем make.bat. Скопируйте в него этот текст:
@echo off
ml /c /coff window.asm
link /subsystem:windows window.obj
pause>nul
<смотреть код>
Далее я буду приводить код не полностью, а только его части (для экономии места), вы можете нажать на <смотреть код>, чтобы отобразить код полностью (в новом окне).
12.4 - Регистрация класса
Теперь зарегистрируем класс в процедуре WinMain. В этой процедуре находится инициализация окна.
Добавьте это к своему файлу:
WinMain PROTO STDCALL :DWORD, :DWORD, :DWORD

.data?
hInstance dd ?
.code
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke WinMain, hInstance, NULL, NULL, SW_SHOWNORMAL
end start
Этот код получит хэндл модуля функцией getmodulehandle, поместите хэндл в переменную hInstance. Этот хэнд модуля очень часто используется в windows API. Затем вызывается процедура WinMain. Это не API функция, а процедура, которую мы теперь определим. Ее прототип: WinMain PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD, функция с 4 параметрами.:
<смотреть код>
Теперь поместите этот код перед end start:
WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD
ret
WinMain endp
Вы не обязаны объявлять функцию winmain. На самом деле, вы совершенно свободны в этом отношении.
hInst это описатель экземпляра (= хэндл модуля),
hPrevInst это хэндл предыдущего экземпляра программы. Под win32 нет такого понятия, как предыдущий экземпляр программы. Каждая программа одна единственная в своем адресном пространстве, поэтому значение этой переменной всегда 0. Это пережиток времен Win16, когда все экземпляры программы запускались в одном и том-же адресном пространстве, и экземпляр мог узнать, были ли запущены еще копии этой программы. Под Win16, если это значение равно NULL, тогда этот экземпляр является первым.


CmdLine указатель на коммандную строку.

CmdShow это флаг, который определяет, как должно быть показанно окно. (Подробнее об этом, вы можете найти в справочнике API функция ShowWindow).
Теперь мы можем написать наш код инициализации в WinMain:
WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
ret
WinMain endp
Здесь мы резервируем две локальные переменные, они нам понадобятся в этой процедуре.
.data
ClassName db "FirstWindowClass",0

.code
WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
; now set all the structure members of the WNDCLASSEX structure wc:
mov  wc.cbSize,SIZEOF WNDCLASSEX
mov  wc.style, CS_HREDRAW or CS_VREDRAW
mov  wc.lpfnWndProc, OFFSET WndProc
mov  wc.cbClsExtra,NULL
mov  wc.cbWndExtra,NULL
push hInst
pop  wc.hInstance
mov  wc.hbrBackground,COLOR_WINDOW
mov  wc.lpszMenuName,NULL
mov  wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov  wc.hIcon, eax
mov  wc.hIconSm, eax
invoke LoadCursor,NULL,IDC_ARROW
mov  wc.hCursor,eax
invoke RegisterClassEx, ADDR wc
ret
WinMain endp
Давайте посмотрим, что получается:
mov  wc.cbSize,SIZEOF WNDCLASSEX
mov  wc.style, CS_HREDRAW or CS_VREDRAW
mov  wc.lpfnWndProc, OFFSET WndProc
mov  wc.cbClsExtra,NULL
mov  wc.cbWndExtra,NULL
Размер структуры инициализирован (это требуется RegisterClassEx). Стиль класса установлен в "CS_HREDRAW or CS_VREDRAW", затем установлен указатель оконной процедуры. Позже вы узнаете, что такое оконная процедура, а сейчас запомните, что вам нужен адрес процедуры WndProc, который вы сможете получить 'offset WndProc'. CbClsExtra и cbWndExtra не используются нами, так что установите их в NULL.
push hInst
pop  wc.hInstance
В wc.hInstance устанавливается хэндл модуля, параметр процедуры WinMain. А почему мы не используем: mov wc.hInstance, hInst? Потому, что команда mov не позволяет перемещать данные из одной ячейки памяти в другую. Здесь мы копируем методом push/pop, значение помещается в стек, а затем извлекается туда, куда нам надо.


mov  wc.hbrBackground, COLOR_WINDOW
mov  wc.lpszMenuName, NULL
mov  wc.lpszClassName, OFFSET ClassName
Цвет фона класса установлен в COLOR_WINDOW, никакое меню не определено (NULL), и lpszClassName установлен адрес строки имени класса, закачивающейся нулем: "FirstWindowClass". Это должно быть уникальное имя, определенное для вашего собственного приложения.
invoke LoadIcon, NULL, IDI_APPLICATION
mov  wc.hIcon, eax
mov  wc.hIconSm, eax
Окну нужен зачек (иконка), а для этого нам нужен хэндл иконки, мы используем LoadIcon, чтобы загрузить значок (иконку) и получать ее хэндл. LoadIcon имеет два параметра: hInstance, и lpIconName. hInstance - хэндл модуля, чей исполняемый файл содержит значок. LpIconName - указатель на строку, которая является названием ресурса значка или ID ресурса. Если вы используете NULL как hInstance, то вы можете выбрать из некоторых стандартных значков. (что мы здесь и делаем, потому что у нас нет ресурса значка). HIconSm - маленький значок, вы можете использовать тот же хэндл и для него.
invoke LoadCursor,NULL,IDC_ARROW
mov  wc.hCursor,eax
Тоже самое и для курсора, только NULL в hInstance, и стандартный тип курсора: IDC_ARROW, стандартная стрелка windows.
invoke RegisterClassEx, ADDR wc
И теперь наконец зарегистрируем класс, используя RegisterClassEx с указателем на wc структуру WNDCLASSEX как параметр.
<смотреть код>
12.5 - Создание окна
Теперь, после того, как вы зарегистрировали класс, вы можете создать из него окно:
HWND CreateWindowEx(
DWORD dwExStyle, // дополнительные стили окна
LPCTSTR lpClassName, // указатель на имя зарегистрированного класса
LPCTSTR lpWindowName, // указатель на имя окна
DWORD dwStyle, // стили окна
int x, // позиция окна по горизонтали
int y, // позиция окна по вертикали
int nWidth, // ширина окна
int nHeight, // высота окна
HWND hWndParent, // хэндл родительского окна
HMENU hMenu, // хэндл меню окна
HINSTANCE hInstance, // Хэндл программного модуля, создающего окно.


LPVOID lpParam // указатель на данные создания окна.
);
dwExStyle и dwStyle это два параметра, которые определяют стиль окна.
lpClassName это указатель на ваше зарегистрированное имя класса.
lpWindowName это имя вашего окна (оно будет в заголовке вашего окна)
x, y, nWidth, nHeight определяют позицию и размер вашего окна.
hWndParent это хэндл окна, которому принадлежит новое окно. Может быть нулевым, если нет родительского окна.
hMenu это хэндл меню окна.
hInstance это хэндл программного модуля, создающего окно.
lpParam дополнительное значение, которое вы можете использовать в своей программе.
.data
AppName "FirstWindow",0
.code
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,400,300,NULL,NULL,\
hInst,NULL
mov hwnd, eax
invoke ShowWindow, hwnd, SW_SHOWNORMAL
invoke UpdateWindow, hwnd
(Обратите внимание, что символ '\' говорит ассемблеру, что следующая строка, это продолжение текущей строки.)
Наш код создаст новое окно, с нашим именем класса, которое мы только что зарегистрировали. Заголовок будет "FirstWindow" (AppName), стиль - WS_OVERLAPPEDWINDOW, который создает перекрытое окно с заголовком, системным меню, с изменяемым размером и кнопками свернуть/развернуть. CW_USEDEFAULT как x и y позиция, установит окну заданные по умолчанию позиции для новых окон. Размер окна - 400x300 пикселей.
Возвращаемое значение функции это хэндл окна, HWND, который сохраняется в локальной переменной hwnd. Затем окно выводится на экран функцией ShowWindow. Функция UpdateWindow гарантирует, что окно будет выведено.
12.6 - Цикл сообщений
Windows может связываться с вашей программой и другими онами, используя сообщения. Оконная процедура (см. ниже в этом уроке) вызывается всякий раз, когда для определенного окна ожидается сообщение. Каждое окно имеет цикл сообщений. Это бесконечный цикл, который проверяет есть ли сообщение для вашего окна, и если есть, то передает сообщение функции dispatchmessage. Эта функция вызовет вашу оконную процедуру. Цикл сообщений и оконная процедура это две разные вещи!!!


WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
LOCAL msg:MSG ;<<<новая переменная
........
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
Вот, как может выглядеть цикл сообщений. .WHILE TRUE, .ENDW повторяют цикл до тех пор, пока eax не стпнет равным 0. Функция GetMessage возвращает 0, если получает сообщение WM_QUIT, которое должно закрыть окно, так что программа должна выйти из цикла сообщений всякий раз, когда GetMessage возвращает 0. Если нет, то сообщение передается функции TranslateMessage (эта функция транслирует нажатия клавиш в сообщения) и затем сообщение посылается окной процедуре, с помощью функции DispatchMessage. Сообщение непосредственно в цикле сообщений состоит из структуры MSG (для этого мы в процедуру добавили LOCAL msg:MSG). Вы можете использовать этот цикл сообщений во всех своих программах.
12.7 - Оконная процедура
Сообщения будут посланы оконной процедуре. Оконная процедура должна всегда выглядеть следующим образом:
WndProc PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD
.code

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
mov eax, uMsg
.IF eax==XXXX
.ELSEIF eax==XXXX
.ELSE
    invoke DefWindowProc, hWnd, uMsg, wParam, lParam
.ENDIF
ret
WndProc endp
Оконная процедура должна всегда иметь 4 параметра:
hWnd содержит хэндл окна.
uMsg это сообщение
wParam это первый параметр для сообщения
lParam второй параметр для сообщения
Сообщения, которые не обрабатывает окно, должны быть переданы функции DefWindowProc, которая занимается их обработкой. Пример оконной прцедуры:
WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
mov eax, uMsg
.IF eax==WM_CREATE
    invoke MessageBox, NULL, ADDR AppName, ADDR AppName, NULL
.ELSEIF eax==WM_DESTROY
    invoke PostQuitMessage, NULL
.ELSE
    invoke DefWindowProc, hWnd, uMsg, wParam, lParam
.ENDIF
ret
WndProc endp
Этот код отображает название приложения, при содании окна. Также, обратите внимание, что я добавил обработку сообщения WM_DESTROY. Это сообщение будет послано, если окно закрывается (т.е. вы нажали закрыть). Приложение должно реагировать на это сообщение функцией PostQuitMessage.
А теперь посмотрите, что у нас получилось:
<смотреть код>
[наверх]

Содержание раздела