Learning Windows Programming
Part 1

WinMain and the Message Loop

Just about every language I have seen uses the 'Hello World' program as the first introduction to programming.
In BASIC it looks like this:

    Print "Hello World"

and in other languages takes only a few more lines of code.
 
So lets look at a 'Hello World' program using POW! and Windows. By the way, it is possible to achieve this in a few lines using POW's Opal functions - but you will need to understand the following to be able to implement any worthwhile Windows programs.


Download the hellow.mod program (this and the next HTM page is included in the ZIP file so that you can take a little more time over it offline).  The program contains just two functions: WinMain and MainWndProc. Every Windows program has a WinMain function.  This is the Entry point to the program.


OK - a couple of new things here.  When a program is executed in Windows it checks to see if there is already a copy of that program running - if so - it does not load a second copy of the program - it uses the existing copy and just creates a new data segment for the program.  Each execution of the program is referred to as an INSTANCE.  So, for any given program, there will only be one copy of the program - but there may be many Instances.  On entry to WinMain, Windows passes over 4 parameters.  The first one is hInstance (called Instance Handle) which is a number uniquely identifying this copy of the program.  The second parameter is hPrevInstance which is the Instance Handle of the most recent previous Instance of the same program.  If no other copies of the same program are running then hPrevInstance will be Zero.


Just briefly, the other two parameters are lpszCmpParam - which is the command-line parameters used when starting this program and, nCmdShw is a parameter stating how the window is to be initially displayed - say, Normal, Minimised, etc
.
PROCEDURE [WINDOWS] WinMain*(
              hInstance: W.HANDLE;           (* current instance      *)
              hPrevInstance: W.HANDLE;   (* previous instance     *)
              lpCmdLine: W.LPSTR;             (* command line          *)
              nCmdShow: INTEGER             (* show-window type      *)
               ): INTEGER;                            (*   (open/icon)         *) 


Getting back to our Instance of the program.  Most programs will be opening a Window when they start up.  A window is always based on a window Class.  The windowClass indentifies, among other rhings,  the Procedure that will be used to process messages for that window.  Since the windowClass will be the same for all Instances of our program we only need to register the windowClass if this is the first Instance of the program - that is - if hPrevInstance = 0.

BEGIN
  IF hPrevInstance = 0 THEN               (* Other instances of app running?  *) 


There are 10 pieces of information you must supply to register a window class:


style:  This is the style of the Window, borders, caption, etc.
lpfnWndProc:  This is the name of the Procedure that is to process all messages for windows of this class
cbClsExtra, cbWndExtra: are maintained internally by windows
hInstance: is the instance handle of the program registering the class
hIcon: sets an Icon for windows created with this class
hCursor: Loads a predefined cursor to be used with this window.
hbrBackGround specifies the background colour of the window
lpszMenuName specifies the window class Menu
lpszClassName specifies the name that we are giving this window class.

After filling out these 10 fields we register the class:

    rc := W.RegisterClass(SYSTEM.ADR(wc));
    END;  (* End IF hPrevInstance=0 *)

If the program contains several Windows with different attributes - they should all be registered here.

(Just a quick note on POW! here.  When calling a Windows Function there are two ways that parameters can be passed.  Parameters may be passed by their value - or they may be passed as the Address of where they occur in memory (Oh - and its not your decision - if Windows wants an address, thats what you pass) - RegisterClass requires the Address of the structure we created to be passed as a parameter.  Since Oberon is a multi-platform language, any commands that require platform dependant coding are implemented using the SYSTEM module)  The SYSTEM.ADR funtion provides the memory address of a variable.

We now perform any coding necessary for initialisation of this Instance.  In this case all we need to do is Create the Window that will be used to display the "Hello Windows" message.  The CreateWindow function allows you to specify a little more detail about window that you want to create. CreateWindow returns a value hWnd (Window Handle).  If your program creates several windows, each one is given a different handle .

Even though we have created the window internally, its still not displayed - there are two more calls necessary:

  r := W.ShowWindow(hWnd, nCmdShow);      (* Show the window  *)

ShowWindow will cause the Window to be placed on the display, the nCmdShow parameter will determine if the window should be displayed Normally, Maximised, Minimised, etc. (A note on the parameters - here we are passing the actually value or contents of the variables hWnd and nCmpShow - not the actually memory address of the variables).

  W.UpdateWindow(hWnd);                   (* Sends WM_PAINT message  *)

UpdateWindow will cause the client area to be painted - it does this by placing a WM_PAINT message on the message queue - We'll get to this on the next page that deals with processing messages.  Yes - I know it sounds a little strange - but here you are creating a message which another part of your program must process - the obvious question is - why can't I just call the Procedure to paint the window from here?  This will become more obvious as you become familiar with Windows programming techniques - for the moment - just trust me!

Our Window is currently displayed on the screen - but we must now make it ready to receive mouse and keyboard input.  We create a loop which gets the next message on the queue - translates keboard input values - and then dispatches the message to the Procedure that been set up to handle messages for windows of this class.

  WHILE W.GetMessage(SYSTEM.ADR(msg),     (* message structure                *)
                     W.NULL,              (* handle of window receiving the ms*)
                     W.NULL,              (* lowest message to examine        *)
                     W.NULL) # 0 DO       (* highest message to examine       *)

    r := W.TranslateMessage(SYSTEM.ADR(msg)); (* Translates virtual key codes *)
    r := W.DispatchMessage(SYSTEM.ADR(msg))   (* Dispatches message to window *)
  END;

If the message retrieved from the queue is anything other than a WM_QUIT then GetMessage returns a non-zero value.  A WM_QUIT cause the program to fall out of the message loop and thus terminate the program.

The DispatchMessage passes the message back to Windows which then sends it to the appropriate Procedure for this window class to process.  In our case MainWndProc. While all of this might seem a lot of messing about - its the basis of handling multitasking within Windows. When there are no messages on the queue for your program to handle - Windows will check for messages for other programs to handle - and switch the processing to their message loop if there is.
 
 

Index Next
Email Brosco