Win32 Assembler Tutorial Chapter 1.2beta
Finished the first part? Then let's continue by creating a window. The code can be used as the basis of further projects.
This time there will be hardly any sourcecode in the text, since most things are language independent. Sourcecodes for NASM/TASM/MASM are attached in the ZIP again.
Note: The best windows.inc to date was made by Iczelion and Hutch. Unfortunately, it only works with MASM. For NASM there is a port of an older MASM-windows.inc which contains everything that's important. The .inc file for TASM I know only covers Win3.x so you have to get the data relevant for Win32 from the other include files.
1. Windows and Window Classes
In Windows a window is not only necessary in order to display something on the screen but it's the basis of all communication in Windows. In order to get and evaluate keypresses, mouse movements, etc., you need a window. Some things can also be polled, but
1. this is not effective and
2. something can happen between two polls which will remain unrecognized.
Not only the standard window but also PopUp menus, buttons, the desktop, TreeViews, etc. are windows. Every window is based on a window class which describes certain attributes of this window. Window classes are especially useful for programs that use several windows. A window can have many attributes to it: minimized, maximized, displayed, hidden, active, inactive, with or without a title bar... The attributes can be changed by both your own program and external programs. (Usually only two states of a button are used: hidden or shown. But you can also minimize or maximize it. That will give your program an entirely new, weird look.)
2. Messages and Message Processing
Messages are sent in order to keep the programs updated about events or requests.
They are created by:
- the operating system, in order to report about the user's action
- the operating system, because a special event occured (e.g. the window was created)
- various programs
- your own program
They are sent to:
- a window with a given handle
- all windows
- a thread (threads will be covered in another tutorial)
A message consists of a MessageID which indicates the type of the message and two parameters with extended information on the event (lParam and wParam).
To process the messages, a MesageQueue is created for the window or the thread. It collects all incoming messages. You evaluate them using the function GetMessage. GetMessage ALWAYS returns a message. That's because this function waits (and gives CPU time to the other programs) until a message has arrived. If you don't want to wait, you can use PeekMessage instead. This function returns immediately. Therefore it also returns whether a message has arrived at all. If the return value of GetMessage equals 0, WM_QUIT has occured.
Using DispatchMessage you forward the message to the window procedure by means of the OS.
A small list of important messages:
WM_CREATE: The window has been created. This message is automatically produced by CreateWindowEx.
WM_QUIT: The program is to exit.
WM_DESTROY: The window is to be destroyed.
WM_PAINT: The window is to be re-painted.
WM_KEYDOWN, WM_KEYUP: I don't think I have to explain these. Useful: The scancode is automatically sent along.
WM_SYSKEYDOWN, WM_SYSKEYUP: Like above, but combined with the ALT keys (menu commands).
WM_MOUSEMOVE: Guess what... Additionally, it contains the state of the mouse buttons. For the mouse buttons itself there are also WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN, WM_RBUTTONUP.
WM_TIMER: This is sent by the standard timer if it's activated. The multimedia timers, however, are a better solution for faster demos and games.
WM_ACTIVATE: The application is activated or terminated. You should evaluate this message in demos and games in order to stop all threads to prevent the program from continuing in the background.
WM_COMMAND: A menu item has been selected.
3. The Window Procedure
Every window has a window procedure in which it evaluates the messages that have been forwarded to it by DispatchMessage.
So far, we have only called Windows functions. Now for a change Windows calls our function! These CALLBACKs match the STDCALL convention, like most of the API functions: The parameters are put onto the stack, the return value will be in EAX, the stack has to be cleared by the called function. Callbacks also occur when using the multimedia timer, for instance.
The parameters of the window procedure are:
Window handle, MessageID, wParam, lParam.
In exactly this order they are located on the stack when the window procedure is called. They correspond to the order of the MSG structure for GetMessage, PeekMessage, and DispatchMessage.
Now you can read the MessageID from the stack. If the message is relevant, you can evaluate it using wParam and lParam. Otherwise leave it to Windows.
If you have evaluated the message yourself, you can return directly using RET 16. The number 16 after RET indicates the number of bytes to be removed from the stack.
If the message has been evaluated incompletely or not at all, you can leave the evaluation to DefaultWndProc. This Windows function contains all necessary elements to handle the messages for a window.
Since our window procedure is only an extension of DefaultWndProc, both functions have the same parameter structure. You can use that by directly JuMPing into the function instead of putting the parameters onto the stack and CALLing DefaultWndProc. Of course this requires leaving the stack unchanged. In this way we can skip creating the parameters and returning to the caller procedure - that's now done by DefaultWndProc.
Menus aren't necessary but they're easy to implement. There are two ways to do it:
a) directly in your program, using the Windows functions (if you're really
bored and don't know what to do),
b) in a resource script, either manually using a text editor or visually using a resource editor.
About the syntax of the menus in our example program:
Win002menu MENU defines a menu called Win002menu. The menu itself is implemented in the following BEGIN...END block.
MENUITEM "menuitem", number creates a menu item called "menuitem" with the given ID number.
POPUP "submenuname" creates a sub-menu item with the given name. The sub-menu is in a BEGIN-END-block again.
MENUITEM SEPARATOR creates a separator line.
Now you can load the menu, either as a menu with a fixed position like the menu below the title bar of a window or as a menu with a variable position like a context menu.
You can (but don't have to) change the outer appearance of the menu using API functions. After using a menu you have to delete it again, otherwise you'll get a memory overflow after some time.
In the example code the menu is used in the easiest way: If you include it in the definition of the window class, it is automatically loaded, activated and deleted.
As soon as the user selects a menu item, a WM_COMMAND message is created. Its item wParam contains the ID of the menu item.
5. Example program
Action #1: Create a window class
RegisterClassEx defines a window class. The following items of the WNDCLASSEX structure are needed:
cbSize: Defines the size of the structure. Use size WNDCLASSEX.
style: Describes the behaviour of the window. See API "Class Styles" (CS_xxx).
lpfnWndProc: Pointer to the window procedure.
cbClsExtra: Needed if the class shall be extended. Can be left 0.
cbWndExtra: The same for a window. For all classes except dialogues, this can be left 0.
hInstance: Marks our program. Is retrieved using GetModuleHandle.
hIcon: Icon for the window. Use 0 for a standard icon, otherwise the handle of a resource.
hCursor: Like icon, 0 = standard cursor.
hbrBackground: Background colour. If 0, the window won't get coloured automatically.
lpszMenuName: Name of the menu resource (0 = no menu).
lpszClassName: Name which identifies the class. You can call it as you want.
hIconSm: Small icon. See normal icon.
Handles for resources have to be created using LoadIcon, LoadCursor or LoadBitmap!
Action #2: Displaying the window
CreateWindowEx creates the window. The parameters are (in the same order they appear in the source):
lpParam: Points to a value for lParam of the WM_CREATE message. Only needed for apps with child windows. Otherwise 0.
hInstance: Already explained somewhere else...
hMenu: Here you can specify a menu handle. Since we've already specified a menu in the class, you can use 0 here. In this case you do not need to compute its handle.
hWndParent: Handle of the ParentWindow. Since we don't create a child window, we use 0.
nHeight: No comments.
nWidth: I need not say anything about this.
y: Related to the positition of the window's top-left corner.
x: Like y.
dwStyle: Various constants combined with OR determining the visual appearance and the behaviour of the window.
lpWindowName: Points to the name of the window. Just for optical reasons.
lpClassName: Points to the name of the class. This class must exist, otherwise there will be no window (see RegisterClassEx and WNDCLASSEX).
dwExStyle: Extension of dwStyle. Supported by Win9x/NT4 and higher.
If successful, the function will return a handle to the window, otherwise it will return 0. If a window has been created, several messages such as WM_CREATE have been DIRECTLY sent to the window procedure. That means, the window procedure is not called by the MessagePump!
Action #3: The MessageLoop aka MessagePump
Using GetMessage, you empty the Message Queue one by one. GetMessage needs four parameters. With the first two parameters, you can restrict the type of messages to be processed, e.g. to mouse-messages only. To get all messages, set both parameters to 0.
The next parameter restricts the received messages to a certain window. You have to set it to the corresponding window handle. If you use 0, you will get all messages related to our program.
The last parameter points to a buffer of a size of seven DWords in which the message is stored.
This function returns three possible values: 1 stands for a successfully received message. 0 means that WM_QUIT has been received. It is a request to quit the program, especially the MessageLoop. You can also get -1, but then, there has been a great bug, like a wrong window handle (which practically means that either your code or the whole system has got fucked up). If you don't want to wait for GetMessage to receive a message, you can also use PeekMessage. This allows you to display your animation in the MessageLoop. It works fine, but a separate thread for it is the smarter solution.
DispatchMessage now forwards the message to a window procedure. If you allow user input, you should, however, first convert the WM_KEYDOWN messages to WM_CHAR messages using TranslateMessage.
Action #4: The Window procedure
Not much to say about that: You check if the current message is interesting. If it is, you can do something with it and return, if it isn't, you can leave it to Windows. You can also process a message and then pass it to DefWndProc, but that's rarely useful.
Perhaps PostQuitMessage might be interesting. Like its name says, it posts a WM_QUIT message. The parameter of this function becomes the lParam of the message and represents a return value. Instead of this function you could also set a flag, but since other sources might create WM_QUIT too, this is the "cleaner" variant.