Część 3: Proste okienko

W tej części, zbudujemy program okienkowy, który wyświetli na ekranie w pełni funkcjonalne okienko;
ściągnij przykładowy plik stąd

Teoria:

Windowsowe programy oparte są na funkcjach API poprrzez ich GUI. To przynosi korzyści zarówno użytkownikom jak i programistom. Użytkownikom, ponieważ nie muszą uczyć się jak posługiwać się GUI każdego nowego programu, GUI windows'owych programów są takie same. Programistom, ponieważ kody GUI już tam są, przetestowane i gotowe do użycia. Wadą jest wzrost złożoności i wymóg przestrzegania określonych zasad. W celu utworzenia lub manipulacji jakimś obiektem GUI, takim jak okno, menu lub ikona, programiści muszą przestrzegać określonych procedur. Ale te restrykcje są rekompensowane modułowością programowania lub paradygmatem obiektowości (OOP).
Opiszę poszczególne kroki wymagane do utworzenia okna na ekranie :
  1. Weź uchwyt swojego programu (wymagane)
  2. Użyj lini poleceń (nie wymagane, chyba że twój program wykorzystuje linie poleceń)
  3. Zarejestruj klasę window (wymagane, chyba że używasz predefiniowanych typów okna, np MessageBox lub dialog box)
  4. Utwórz okno (wymagane)
  5. Wyświetl okno na ekranie (wymagane, chyba że nie chcesz od razu wyświetlać okna)
  6. Odśwież obszar roboczy okna
  7. Wprowadź nieograniczoną pętlę, sprawdzającą wiadomości wysyłane przez Windows
  8. Gdy pojawią się wiadomości, będą przetwarzane przez przystosowaną do tego funkcęję, która jest odpowiedzialna za okno
  9. Wyłącz program, gdy użytkownik zamknie okno
Jak widzisz, struktura programu Windows jest raczej złożona w porównaniu do programu DOS'owego. Ale świat Windows jest całkowicie różny od światu DOS. Prgramy Windows muszą wzajemnie ze sobą bezkonfliktowo współegzystować . Dlatego muszą spełniać ścisłe reguły. Jako programista, musisz również być bardziej dokładny w programowaniu pod względem stylu i zasad.

Zawartość:

Poniżej jest kod źródłowy prostego programu z okienkiem. Zanim przejdziesz do szczegółów programowania w Win32 ASM, wskażę kilka ważnych punktów, które ułatwią ci programowanie. .386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib            ; wywołuje funkcje w user32.lib i kernel32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

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

Analiza:

Możesz w tym momencie poczuć się zniechęcony, ponieważ taki prosty program Windows wymaga tak dużo kodu. Ale większość tego kodu jest po prostu *szablonem*, który możesz kopiować z jednego pliku źródłowego do innego. Lub- jak wolisz- możesz wstawić część tego kodu do biblioteki aby mógł być użyty jako początek i koniec (prolog i epilog) właściwego kodu. Możesz tylko wpisać kod, który znajduje się w funkcji WinMain. W rzeczywistości tak postępują  kompilatory języka C. Umożliwiają ci one zapisywanie tego co się znajduje w WinMain zamiast martwić się o dodatkowe dane. Jedyną różnicą jest, że musisz mieć funkcję o nazwie WinMain, w przeciwnym wypadku kompilator C nie będzie mógł połączyć twojego kodu z prologiem i epilogiem. Nie masz takich restrykcji w języku asemblera. Możesz użyć jakiejkolwiek innej funkcji zamiast WinMain lub nie używać w ogóle żadnej funkcji.
Przygotuj się. To będzie bardzo długi tutorial. Przeanalizujmy bardzo dokładnie ten program! Pierwsze trzy linie są "konieczne". .386 mówi MASM że będziemy używać zbioru instrukcji 80386 w tym programie. .model flat,stdcall mówi MASM że nasz program używa adresowania typowego dla modelu flat memory. Również użyjemy konwencji przekazywania parametrów stdcall jako domyślny w naszym programie.
Następnie mamy prototyp funkcji dla WinMain. Ponieważ będziemy później wywoływać WinMain, musimy zdefiniować najpierw jej prototyp aby móc wywołać ją.
Musimy dołączyć windows.inc na początku kodu źródłowego. Zawiera on ważne struktury i stałe, które są wykorzystywane przez nasz program. Plik include, windows.inc, jest po prostu plikiem tekstowymi. Możesz go otworzyć za pomocą dowolnego edytora. Proszę zauważyć że windows.inc nie zawiera wszystkich struktur i stałych (jeszcze). Razem z Hutch'em pracujemy nad tym. Możesz dołaczyć do niego nowe dane jeżeli ich nie ma w tym pliku.
Nasz program wywołuje funkcje API które rezydują w user32.dll (na przykład: CreateWindowEx, RegisterWindowClassEx) i kernel32.dll (ExitProcess), więc musimy przyłączyć nasz program do tych dwu bibliotek. Następne pytanie: skąd mam wiedzieć która biblioteka powinna być przyłączona do mojego programu? Odpowiedź: Musisz wiedzieć gdzie znajdują się funkcje API wywoływane przez program. Na przykład, jeśli wywołujesz funkcję API w gdi32.dll, musisz łączyć z gdi32.lib.
To jest właściwość MASM. TASM działa inaczej: po prostu przyłącza program do jednego i tylko jednego pliku: import32.lib. Następnie są sekcje "DATA".
W .DATA, deklarujemy dwa zakończone zerami łańcuchy (łańcuchy ASCIIZ): ClassName jest nazwą naszej okienkowej klasy a AppName jest nazwą naszego okna. Zauważ, że te dwie zmienne są zainicjowane.
W .DATA?, zadeklarowane są dwie zmienne: hInstance (uchwyt naszego programu) i CommandLine (linia poleceń naszego programu). Nieznane typy danych, HINSTANCE i LPSTR są w rzeczywistości nowymi nazwami dla DWORD. Możesz je zobaczyć w pliku windows.inc. Zauważ, że wszystkie zmienne w sekcji .DATA? są nieinicjowane, więc nie przechowują żadnej specjalnej wartości na początku, ale chcemy zarezerwować miejsce do przyszłego wykorzystania. .CODE zawiera wszystkie instrukcje. Kod musi rezydować pomiędzy <etykietą startową>: i końcem <etykiety startowej>. Nazwa etykiety nie jest ważna. Możesz nazwać ją jak chcesz tylko żeby była nazwą unikalną i nie zakłócała konwencji nazw asemblera MASM.
Naszą pierwszą instrukcją jest wywołanie GetModuleHandle w celu uzyskania instancyjnego uchwytu naszego programu. Pod Win32, instancyjny uchwyt i modułowy uchwyt to jedno i to samo. Możesz uważać instancyjny uchwyt za numer ID twojego programu. Jest on używany jako parametr dla funkcji API, które program musi wywoływać, więc jest dobrym pomysłem uzyskać go na samym początku.
Uwaga: Aktualnie pod win32, instancyjny uchwyt jest liniowym adresem twojego programu w pamięci.
Odnośnie zwracania wartości z funkcji Win32; jeżeli wartość jest zwracana z funkcji, można ją znaleźć w rejestrze eax. Wszystkie inne wartości są zwracane przez zmienne dostarczane w liście parametrów funkcji, którą definiujesz w celu wywołania.
Funkcja Win32, którą wywołujesz będzie prawie zawsze zachowywała rejestry segmentowe oraz rejestry ebx, edi, esi i ebp. W przeciwieństwie do rejestrów ecx i edx, które są uważane za rejestry indeksujące i są zawsze nie zdefiniowane podczas powrotu z funkcjii Win32.
Uwaga: Nie oczekuj zachowania wartości w eax, ecx, edx podczas wywołania funkcji API.
Powyższa linia oznacza: kiedy wywołujesz funkcję API, oczekujesz zwrotu wartości w eax. Jeżeli jakaś twoja funkcja będzie wywołana przez Windows, musisz również przestrzegać następującej reguły: zachowaj i odtwórz wartości rejestrów segmentowych, ebx, edi, esi i ebp przy wywołaniu funkcji, inaczej twój program się szybko wysypie, to dotyczy także twoich procedur okienkowych i powrotów z funkcji.
wywołanie GetCommandLine nie jest konieczne jeżeli twój program nie interpretuje linii poleceń. W tym przykładzie, pokażę ci jak to wywołać w przypadku, gdybyś potrzebował w swoim programie.
Następnie jest wywołanie WinMain. Tutaj otrzymuje cztery parametry: uchwyt instancyjny programu, uchwyt poprzedniej instancji program, linię poleceń i stan okna przy pierwszym wyświetleniu . Pod Win32, NIE MA poprzedniej instancji. Każdy program jest sam w swojej przestrzeni adresowej, więc wartość hPrevInst zawsze jest 0. To jest pozostałość po Win16 kiedy wszystkie instancje programu uruchamiane były w tym samym miejscu pamięci i wymagany był system instancji aby móc określić która instancja programu jest pierwsza. Pod win16, gdy hPrevInst jest NULL, wówczas ta instancja jest pierwsza.
Uwaga: Nie musisz deklarować funkcji o nazwie WinMain. W rzeczywistości masz całkowitą wolność wyboru nazwy. Nie musisz w ogóle używać ekwiwalentu funkcji WinMain. Możesz wkleić kod wewnątrz funkcji WinMain przy komendzie GetCommandLine i twój program będzie działał prawidłowo.
Przy powrocie z WinMain, eax jest wypełniony kodem powrotu. Przekazujemy ten kod powrotu jako parametr do ExitProcess który kończy naszą aplikację.

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.


[Tutorial 4]