WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
.DATA
; inicjowanie danych
ClassName db "SimpleWinClass",0
; nazwa naszej klasy window
AppName db "Our First Window",0
; nazwa naszego okienka
.DATA?
; niezainicjowane dane
hInstance HINSTANCE ?
; Instancyjny uchwyt naszego programu
CommandLine LPSTR ?
.CODE
; Tu zaczyna się nasz kod
start:
invoke GetModuleHandle, NULL
; weź uchwyt naszego programu.
; W Win32, hmodule==hinstance mov hInstance,eax
mov hInstance,eax
invoke GetCommandLine
; weź linię poleceń. Nie musisz wywoływać tej funkcji JEŻELI
; twój program nie uwzględnia linii poleceń
.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine,
SW_SHOWDEFAULT
; wywołanie głównej funkcji
invoke ExitProcess, eax
; zakończenie programu. Kod powrotu jest zwracany w eax z WinMain.
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
; tworzenie localych zmiennych na stosie
LOCAL msg:MSG
LOCAL hwnd:HWND
mov
wc.cbSize,SIZEOF WNDCLASSEX
; wypełnienie wartości struktury wc
mov
wc.style, CS_HREDRAW or CS_VREDRAW
mov
wc.lpfnWndProc, OFFSET WndProc
mov
wc.cbClsExtra,NULL
mov
wc.cbWndExtra,NULL
push
hInstance
pop
wc.hInstance
mov
wc.hbrBackground,COLOR_WINDOW+1
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
; rejestrowanie naszej klasy window
invoke CreateWindowEx,NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
mov
hwnd,eax
invoke ShowWindow,
hwnd,CmdShow
; wyświetlenie naszego okienka na ekranie
invoke UpdateWindow,
hwnd
; odświeżenie obszaru roboczego
.WHILE TRUE
; wprowadzenie pętli sprawdzania wiadomości
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov
eax,msg.wParam
; zwraca kod wyjścia w eax
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT,
wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
; gdy użytkownik zamyka nasze okno
invoke PostQuitMessage,NULL
; zakończenie naszej aplikacji
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
; domyślne przetwarzanie wiadomości
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
Powyższa linia jest deklaracją funkcji WinMain. Zauważ parametry występujące po dyrektywie PROC. To są parametry, które otrzymuje WinMain od wywołującego. Możesz odwoływać się do tych parametrów przez nazwę zamiast przez operacje na stosie. W dodatku, MASM wygeneruje kod prologu i epilogu dla funkcji. Więc nie musimy się koncentrować na ramkach stosu przy wchodzeniu do funkcji i wychodzeniu.
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
Dyrektywa LOCAL dokonuje alokacji pamięci ze stosu dla zmiennych lokalnych używanych przez funkcję. Zbiór dyrektyw LOCAL musi być obowiązkowo poniżej dyrektywy PROC. Po dyrektywie LOCAL jest obowiązkowo <nazwa lokalnej zmiennej>:< typ zmiennej>. Więc LOCAL wc:WNDCLASSEX mówi MASM aby przydzielił pamięć ze stosu o rozmiarze struktury WNDCLASSEX dla zmiennej o nazwie wc. Możemy odwoływać się do wc w naszym kodzie bez potrzeby dokonywania trudnych operacji na stosie. To jest rzeczywiście duża zaleta. Oznacza, że lokalne zmienne nie mogą być użyte poza funkcją , gdzie zostały utworzone i są automatycznie niszczone kiedy funkcja wraca do wywołującego. Inna cecha jest taka, że nie możesz inicjować lokalnych zmiennych automatycznie, ponieważ są one tylko pamięcią stosu przydzieloną dynamicznie kiedy funkcja jest uruchamiana. Musisz ręcznie skojarzyć je z żądanymi wartościami po dyrektywie LOCAL.
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
hInstance
pop
wc.hInstance
mov
wc.hbrBackground,COLOR_WINDOW+1
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
Linie powyżej są naprawdę
proste w zamyśle. To zajmuje tylko kilka linii poleceń.
Koncepcją tych linii jest klasa window.
Klasa window jest niczym więcej jak tylko specyfikacją okna.
Definiuje ona kilka ważnych własności okna takich jak jego ikona,
rodzaj kursora, funkcje odpowiedzialne za to okno, jego kolor itp. Tworzysz
okno na podstawie klasy okna. To jest rodzaj koncepcji programowania
zorientowanego obiektowo. Jeżeli będzież chciał utworzyć więcej niż jedno okno
z tymi samymi właściwościami, wówczas jest powód aby zapisać wszystkie te właściwości
tylko w jednym miejscu i odwoływać się do nich kiedy trzeba.
Ta metoda zaoszczędzi sporo pamięci przez uniknięcie dublowania informacji.
Pamiętaj, Windows został zaprojektowany w przeszłości, kiedy kości pamięci
były niedostępne i większość komputerów dysponowała pamięcią 1 MB.
Windows musi być bardzo efektywny w wykorzystywaniu wolnych zasobów
pamięci.
Ważne jest, że: jeżeli zdefiniujesz swoje własne okno, musisz wypełnić wszystkie
wymagane właściwości swojego okna w strukturze WNDCLASS lub WNDCLASSEX i
wywołać RegisterClass lub RegisterClassEx zanim będziesz mógł utworzyć swoje okno.
Musisz jedynie zarejestrować klasę okna jeden raz dla każdego typu okna, z którego
chcesz je tworzyć.
Windows posiada kilka predefiniowanych
klas Window, takich jak przycisk i okienko tekstowe. Dla tych okienek (lub kontrolek),
nie musisz rejestrować klasy okna, wywołaj tylko CreateWindowEx z
predefiniowaną nazwą klasy.
Ważną składową w WNDCLASSEX jest lpfnWndProc.
lpfn oznacza long pointer (wskaźnik typu long) do funkcji.
Pod Win32 nie ma żadnych "near" lub "far" (bliskich i dalekich) wskaźników, tylko wskaźniki,
z powodu nowego modelu pamieci FLAT. Lecz jest to znowu pozostałość po Win16.
Każda klasa okna musi być skojarzona z funkcją nazywaną procedurą okna.
Ta procedura jest odpowiedzialna za przechwytywanie wiadomości z wszystkich okien utworzonych z
odpowiedniej klasy okna. Windows wyśle wiadomosci do procedury okna
dla oznaczenia ważnych zdarzeń za które to okno jest odpowiedzialne, takich jak
dane z klawiatury lub myszy.
To jest zadanie procedury okna odpowiadać w sposób inteligentny na każdą wiadomość
okienkową, jaką otrzymuje. Spędzisz wiele czasu pisząc uchwyty zdarzeń w procedurze okna.
Poniżej opiszę każdą składową WNDCLASSEX:
WNDCLASSEX STRUCT DWORD
cbSize
DWORD ?
style
DWORD ?
lpfnWndProc
DWORD ?
cbClsExtra
DWORD ?
cbWndExtra
DWORD ?
hInstance
DWORD ?
hIcon
DWORD ?
hCursor
DWORD ?
hbrBackground
DWORD ?
lpszMenuName
DWORD ?
lpszClassName
DWORD ?
hIconSm
DWORD ?
WNDCLASSEX ENDS
cbSize:
Rozmiar struktury WNDCLASSEX w bajtach. Możemy użyć operatora SIZEOF w celu uzyskania wartości
.
style:
Styl okien tworzonych z tej klasy. Możesz łączyć kilka styli razem używając operatora "or".
lpfnWndProc:
Adres procedury okna odpowiedzialnej za okna tworzone z tej klasy.
cbClsExtra:
Określenie liczby dodatkowych bajtów do przydzielenia dla struktury klasy okna.
System operacyjny inicjuje bajty ustawiając je na zero. Możesz
zapisać klasę okna - tutaj określając dane.
cbWndExtra: Określa
liczbę dodatkowych bajtów do przydzielenia dla instancji okna. System operacyjny ustawia tę wartość
początkowo na zero. Jeżeli aplikacja używa struktury WNDCLASS do zarejestrowania okienka
dialogowego tworzonego przez dyrektywę CLASS w pliku zasobów, musi być ustawiona ta
składowa na DLGWINDOWEXTRA.
hInstance:
Instancyjny uchwyt modułu.
hIcon:
Uchwyt ikony. Weź go z wywołania LoadIcon.
hCursor:
Uchwyt kursora. Weź go z wywołania LoadCursor.
hbrBackground:
Kolor tła okien tworzonych z klasy.
lpszMenuName:
Domyślny uchwyt menu dla okien tworzonych z klasy.
lpszClassName:
Nazwa tej klasy okna.
hIconSm:
Uchwyt do małej ikony, która jest skojarzona z klasą okna. Jeżeli ta składowa
jest NULL, system przeszukuje zbiory ikon określone przez składową hIcon w celu
zastosowania ikony o rozmiarze *small icon*.
invoke CreateWindowEx, NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
Po zarejestrowaniu klasy okna, możemy wywołać CreateWindowEx w celu utworzenia naszego okna opartego na podporządkowanej klasie okna. Zauważ, że jest 12 parametrów dla tej funkcji.
CreateWindowExA proto dwExStyle:DWORD,\
lpClassName:DWORD,\
lpWindowName:DWORD,\
dwStyle:DWORD,\
X:DWORD,\
Y:DWORD,\
nWidth:DWORD,\
nHeight:DWORD,\
hWndParent:DWORD
,\
hMenu:DWORD,\
hInstance:DWORD,\
lpParam:DWORD
Zobaczmy dokładny opis każdego parametru:
dwExStyle:
Dodatkowe style okna. To jest nowy parametr, dodany do starego CreateWindow.
Możesz tu wprowadzić nowe style okna dla Windows 95 & NT.Możesz
określić swój własny styl okna w dwStyle ale jeśli chcesz jakieś specjalne style,
musisz wyspecyfikować je tutaj. Możesz użyć NULL, jeśli nie chcesz dodatkowych styli okna.
lpClassName: (Wymagane).
Adres łańcucha ASCIIZ, zawierającego nazwę klasy okna, którego chcesz użyć jako szblonu
dla tego okna. Clasa może być twoją własną zarejestrowaną klasą lub predefiniowaną klasą okna.
Jak podano powyżej, każde okno które stworzysz musi być oparte na klasie okna.
lpWindowName: Adres
łańcucha ASCIIZ, zawierający nazwę okna. To się pokaże na pasku tytułowym okna.
Jeżeli ten parametr będzie NULL, tytuł okna będzie pusty.
dwStyle:
Style okna. Możesz tu określić sposób pojawiania się okna.
Wprowadzenie NULL jest możliwe, ale wtedy okno nie będzie miało systemowego menu ani
przycisków minimalizacji-maksymalizacji ani też przycisku zamknięcia okna. Okno nie będzie
mogło być w ogólw używane. Będziesz zmuszony użyć kombinacji klawiszy Alt+F4 dla zamknięcia go.
Większość powszechnie używanych styli to WS_OVERLAPPEDWINDOW. Styl okna jest tylko
bitową flagą. Dlatego możesz łączyć ze sobą różne style okna poprzez operator lub "or"
w celu uzyskania wymaganego sposobu wyświetlenia okna. Styl WS_OVERLAPPEDWINDOW
jest obecnie połączeniem najczęściej używanych styli okien w tej metodzie.
X,Y:
Współrzędne górnego lewego rogu okna. Normalnie ta wartość mowinna być CW_USEDEFAULT,
to jest, chcesz aby Windows zadecydował za ciebie, gdzie ustawić okno na ekranie.
nWidth, nHeight:
Szerokość i wysokość okna w pikselach. Możesz również użyć CW_USEDEFAULT aby pozwolić Windows
wybrać właściwą szerokość i wysokość.
hWndParent:
Uchwyt do okna macierzystego wzgędem tego okna (jeśli istnieje). Ten parametr mówi
Windows czy to okno jest dzieckiem (podporządkowane) jakiegoś innego okna i,
jeżeli tak, które okno jest rodzicem. Zauważ, że to nie jest relacja rodzic-dziecko
interfejsu wielodokumentowego (MDI). Okna dzieci nie są związane z obszarem roboczym
okna rodzica. Ta relacja jest określona dla użytku wewnętrznego Windows. Jeżeli okno rodzic
jest niszczone, wszystkie okna dzieci będą automatycznie zniszczone. To jest naprawdę proste.
Ponieważ w naszym przykładzie jest tylko jedno okno, ustawiamy ten parametr na NULL.
hMenu:
Uchwyt do okienkowego menu. NULL, jeżeli będzie używane menu klasowe. Cofnij się
do składowej struktury WNDCLASSEX, lpszMenuName. lpszMenuName
określa domyślne *default* menu dla klasy okna. Każde okno utworzone z tej klasy będzie miało
domyślnie to samo menu. Chyba, że określisz inne znaczenie menu, *overriding*,
poprzez określenie parametru okna: hMenu. hMenu jest aktualnie parametrem o podwójnym znaczeniu.
Jest typem okna predefiniowanym, (np kontrolki). Takie kontrolki nie mają własnego menu,
hMenu jest używane jako numer identyfikcji- ID. Windows może zdecydować czy
hMenu jest rzeczywiście uchwytem menu czy numerem ID poprzez parametr lpClassName.
Jeżeli to jest predefiniowana klasa okna, hMenu jest numerem ID. Jeżeli nie, wtedy jest to
uchwyt do menu okienkowego.
hInstance:
Instancyjny uchwyt do modułu programu, który tworzy okno.
lpParam: Opcjonalny
wskaźnik do struktury danych przekazywanych do okna. To jest wykorzystywane przez okno MDI
do przekazania danych CLIENTCREATESTRUCT. Normalnie, ta wartość jest ustawiona na NULL,
co oznacza, że nie ma żadnych danych przekazywanych poprzez CreateWindow().
Okno może uzyskać wartość tego parametru przez odwołanie się do funkcji GetWindowLong.
mov hwnd,eax
invoke ShowWindow,
hwnd,CmdShow
invoke UpdateWindow,
hwnd
Przy prawidłowym powrocie z funkcji CreateWindowEx, uchwyt okna jest przekazywany w eax. Musimy przechować tę wartość do późniejszego wykorzystania. Okno, które właśnie utworzyliśmy nie jest automatycznie wyświetlane. Musimy wywołać ShowWindow z uchwytem okna oraz z koniecznym statusem wyświetlania *display state* okna aby je wyświetlić na ekranie. Następnie możesz wywołać UpdateWindow w celu takim żeby okno odświeżyło swój obszar roboczy. Ta funkcja jest użyteczna kiedy chcesz uaktualnić zawartość obszaru roboczego. Możesz jednak pominąć to wywołanie.
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
Teraz nasze okno jest już na ekranie.
Ale nie może odbierać wprowadzanych danych z zewnątrz. A zatem musimy je
poinformować o istotnych zdarzeniach. Robimy to za pomocą pętli sprawdzania wiadomości.
Tylko jedna pętla sprawdzania wiadomości znajduje się w każdym module. Ta pętla kontynuuje sprawdzanie
docierających z Windows wiadomości poprzez wywołanie GetMessage. GetMessage przekazuje
wskaźnik do struktury MSG w Windows. Ta struktura MSG będzie wypełniana informacjami
o wiadomościach, które Windows chce wysłać do okienka w module.
Funkcja GetMessage nie kończy swojego działania dopóki jest jakaś wiadomość dla
modułowego okienka. Przez ten czas, Windows może sprawować kontrolę nad innymi programami.
To jest schemat współpracy w ramach wielozadaniowości platformy Win16.
GetMessage zwraca FALSE gdy odbierze wiadomość WM_QUIT poprzez pętlę sprawdzania wiadomości,
zakończy tę pętlę a następnie zakończy program.
TranslateMessage jest użyteczną funkcją,
która pobiera dane z klawiatury i generuje nową wiadomość (WM_CHAR), która jest
umieszczana w kolejce wiadomości. Wiadomość WM_CHAR zawiera wartość ASCII wciśniętego klawisza,
w którym to systemie łatwiej pracować, aniżeli w przypadku skanowania klawiatury.
Można ominąć to wywołania, jeśli program nie pobiera danych z klawiatury.
DispatchMessage wysyła dane wiadomości
do procedury okna odpowiedzialnej za okno, dla którego są przeznaczone wiadomości.
mov eax,msg.wParam
ret
WinMain endp
Jeżeli pętla sprawdzania wiadomości się zakończy, zapisywany jest w składowej wParam struktury MSG kod zakończenia. Możesz przechować ten kod zakończenia w rejestrze eax aby zwrócić go do Windows. Teraz Windows nie zrobi żadnego użytku z tą wartościa, ale lepiej tak czynić dla bezpieczeństwa i dla zachowania reguł.
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
To jest nasza procedura okna. Nie musisz nazywać jej WndProc. Pierwszy parametr, hWnd, jest okienkowym uchwytem tego okna, dla którego jest przeznaczona wiadomość. uMsg jest tą wiadomością. Zauważ, że uMsg nie jest strukturą MSG. To jest tylko numer. Windows definiuje setki wiadomości, z których większość nie będzie interesująca dla twojego programu. Windows wyśle odpowiednią wiadomość do okna w przypadku gdy coś ważnego dla tego okna się wydarzy. Procedura okna odbiera tę wiadomość i inteligentnie na nią reaguje. wParam i lParam są właśnie dodatkowymi parametrami do wykorzystania przez określone wiadomości. Wiadomości wysyłają odpowiednie dane jako dowiązanie do samych wiadomości. Te dane są przesyłane do procedury okna poprzez właśnie lParam i wParam.
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
Tu jest część krytyczna. To jest
miejsce gdzie rezyduje większość inteligencji twojego programu. Kod, który odpowiada
na każdą wiadomość Windows jest w procedurze okna. Nasz program musi sprawdzać wiadomości
Windows w celu zobaczenia czy dana wiadomość jest dla niego interesująca. Jeżeli tak,
to zrób cokolwiek co musisz zrobić w odpowiedzi na tę wiadomość a następnie zwróć zero w eax.
Jeżeli nie, MUSISZ wywołać procedurę DefWindowProc, przekazując do niej wszystkie parametry
jakie otrzymałeś w celu przetwarzania domyślnego..Ta procedura DefWindowProc
jest funkcją API, która przetwarza wiadomości, którymi twój program nie jest zainteresowany.
Tą jedyną wiadomością, na którą MUSISZ
odpowiedzieć jest WM_DESTROY. Ta wiadomość jest wysyłana do twojej procedury okna
kiedy okno jest zamykane. Podczas gdy twoja procedura okna otrzymuje tę wiadomość,
okno jest już usunięte z ekranu. Po poleceniu destrukcji, powinieneś przygotować się
do powrotu do Windows. W odpowiedzi na to, możesz wykonać czynności przygotowujące do powrotu
do Windows. Nie masz wyboru oprócz wyjścia, gdy pojawia się ten stan.
Jeżeli chcesz mieć szansę powstrzymania użytkownika od zamknięcia okna, powinieneś
użyć wiadomości WM_CLOSE. Teraz znowu o WM_DESTROY. Po wykonaniu przygotowań, musisz
wywołać PostQuitMessage, która prześle WM_QUIT spowrotem do twojego modułu. WM_QUIT
wykona GetMessage zwracając zero w eax, co znowu zatrzyma pętlę sprawdzania wiadomości i
wyjdzie do Windows. Możesz wysłać wiadomość WM_DESTROY do swojej własnej procedury okna
przez wywołanie funkcji DestroyWindow.