CS-360
Fall, 2000
Class 2
R. Eckert

MICROSOFT WINDOWS WIN32 API PROGRAMMING --
AN EXAMPLE OF EVENT-DRIVEN, GRAPHICS-ORIENTED PROGRAMMING

EXAMPLE--The user moves the mouse cursor over a program's window area
         and clicks the left mouse button.

  Windows decodes hardware signals from the mouse.
  Figures out which window the user has selected.
  Sends a message to its program:
                   "User has clicked over (X,Y)"
                   "Do something and return control to me"

  Program should:
                 Read message data,
                 Do what's needed
                 Return control to Windows

  The following figure illustrates how Windows responds to events and
  interacts with the various applications that are running.



OVERVIEW OF THE STRUCTURE OF A WINDOWS PROGRAM (2 main tasks)-

  1. Initial activities
  2. Process messages from Windows (the message loop)


PSEUDO CODE--

  Initialize variables, memory space

  Create and show the program's window

  Loop
     Fetch any message sent from Windows to this program
     If message is QUIT
        Terminate program, return control to Windows
     If message is something else
        Do appropriate actions based on the message ID and parameters
     Return control to Windows
  End Loop


ESSENTIAL PARTS OF A WINDOWS PROGRAM--

I. THE SOURCE PROGRAM (.C or .CPP FILE):

  A. The WinMain() function--

     0. Include files, variable/function declarations, initialization, etc.

     1. Register the class from which the program window will be derived.

     2. Create a window based on a registered class.

     3. Show the window and cause it to update (repaint) its client area.

     4. The Message Loop--Get messages from Windows and dispatch back to
        Windows for forwarding to the correct callback message-processing
        function. Messages are usually the result of user actions.


  B. The WndProc() function--A callback function that acts on messages
     from Windows. It's a callback because Windows calls it.


II. THE RESOURCE SCRIPT (.RC FILE):

  Resource data--Windows static data.

     Separate from code and dynamic data.
     Compiled by a separate "Resource Compiler".
     Examples: Keyboard Accelerators, Bitmaps, Cursors, Dialog Box
               Definitions, Fonts, Icons, Menus, String Tables.

     Separation of static resources and program code means:
       Reduced memory demands.
       Separation of programmer and designer tasks.
       Changes can be made to the user interface without touching the code.

NOW WE LOOK AT THE DETAILS (Refer to the examples WINAPP1 and WINAPP2)--

I. THE SOURCE PROGRAM (.CPP FILE)

#include <windows.h>        // Windows header file - always included
                            // Contains constant and function definitions

Declare the Window Procedure--

  LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);

A. THE WinMain() FUNCTION--

  int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                                     LPSTR lpszCmdLine, int nCmdShow);

  [Under Visual C++ 6.0, the first two parameters must be of type HINSTANCE.]

  Every Windows program must have a WinMain()--
     -Like main(), it starts first.
     -It returns an integer exit code to Windows which does nothing with it.
     -PASCAL means left-to-right parameter passing on stack, which is faster 
      than the C convention.

  Four parameters are passed from Windows to WinMain():

   -hinstance: Instance handle--an ID # created by Windows when application starts.
    Each instance has a unique handle which identifies the application's data.

    [Handle--A pointer to a pointer. Handles locate objects in memory even though
    Windows moves things around.]

   -hPrevInstance: If another copy of the program is started, this will
    contain the hInstance value for the last copy started; it is NULL (0) if no
    other copy is running. (Under Win32 this is not used.)

   -lpszCmdLine: A pointer to a character string containing the command line
    arguments passed to the program. (like argc, argv in a DOS C program).

   -nCmdShow: An integer passed to the program's ShowWindow() function.
    Windows is telling the application whether its window is to appear
    minimized, as an icon, normal, or maximized when it's first displayed.

  Hungarian Notation--Used very commonly in Windows programs:
    -Helps to clarify types of variables.
    -Precede a variable name with key letters representing its type.
    -Named after Hungarian Microsoft programmer, Charles Simonyi.

  prefix   data type
  ---------------------------------------------------
  by    BYTE (unsigned char)
  b     BOOL (int, use only TRUE and False, values: 1 and 0)
  c     char
  dw    DWORD (double word, 4-byte unsigned long int)
  fn    function
  h     handle. An ID value Windows uses internally to keep track of memory
        blocks, window ID values, etc.
  i     int (two byte signed)
  l     long (4 bytes)
  n     short (int) near pointer
  p     pointer
  s     character string
  sz    null-terminated character string
  w     word (two bytes)

  These can be combined -- e.g., lpsz: long pointer to a null-terminated string.

  1. REGISTERING THE WINDOW CLASS (New Windows Classes)

  New windows are created from "classes". All windows of a given class have
  the same properties: background color, cursor shape, pass Windows
  messages to the same function. We can use predefined classes (e.g. "BUTTON")
  or define and register a new class.

  There can be many instances based on the same class.

  Windows can only be created if their class has been "registered"--

  RegisterClass()--Takes one parameter--a pointer to a WNDCLASS structure.
  The program must fill in the members of the class describing the window
  class before calling RegisterClass().

  typedef struct tagWNDCLASS
  {
  UINT    style;         //bitwise or'd binary flags specify window style
  LRESULT CALLBACK lpfnWndProc)();  // pointer to message-processing function
  int     cbClsExtra;    //count of extra bytes after window class, usually 0
  int     cbWndExtra;    //count of extra bytes after window instance
                         //both could be used to reserve space for extra data
  HINSTANCE  hInstance;  //creating program's instance handle
  HICON   hIcon;         //handle to the window class icon
  HCURSOR hCursor;       //handle to the window class cursor shape
  HBRUSH  hBackground;   //window class background brush
  LPSTR   lpszMenuName   //pointer to resource name of class menu
  LPSTR   lpszClassName; //pointer to name of the window's class
  }  WNDCLASS;

  To register the class of our window, we must set up the members of a
  WNDCLASS variable and call RegisterClass() as in the following sample code:

         WNDCLASS wndclass;
         wndclass.style          = CS_HREDRAW | CS_VREDRAW ;
         wndclass.lpfnWndProc    = WndProc ;
         wndclass.cbClsExtra     = 0 ;
         wndclass.cbWndExtra     = 0 ;
         wndclass.hInstance      = hInstance ;
         wndclass.hIcon          = LoadIcon (hInstance, "MYICON") ;
         wndclass.hCursor        = LoadCursor (NULL, IDC_ARROW) ;
         wndclass.hbrBackground  = (HBRUSH)GetStockObject (WHITE_BRUSH) ;
         wndclass.lpszMenuName   = "MYMENU" ;
         wndclass.lpszClassName  = "MyClass" ;
         if (!RegisterClass (&wndclass))
               return 0 ;

  If the call to RegisterClass(&wndclass) is successful, a non-zero result
  will be returned. If not, we should return a 0 to Windows, which causes
  execution to terminate.

  The WNDCLASS fields for WINAPP1.CPP window are:

  .style-- CS_HREDRAW|CS_VREDRAW--bitwise or operator. It specifies that
  windows of this class should be redrawn if either the horizontal or
  vertical size changes.

    CS_HREDRAW = 00000010B
    CS_VREDRAW = 00000001B
    or'd value = 00000011B

  These are examples of binary flags--each bit means something; the bitwise or
  operation is a way to tell Windows that several conditions are to be set.
  It's a way to specify combinations. (See "Help | Search | WNDCLASS").

  .lpfnWndProc--WndProc: the name (address) of the function to be called
  when windows sends messages. It could have any name you like, but that name
  must be registered. Here the name used is WndProc. This function must be
  declared--usually at the top of the source file or in a header file
  included at the top of the source file.

  .cbClsExtra & .cbWndExtra--Here no extra memory is allocated for this class.

  .hInstance--Specifies which instance the WinProc() lies in. It's this
  instance (passed as a parameter by Windows to WinMain()). It's used as a
  unique identification for the application calling RegisterClass().

  .hIcon, .hCursor--Use Windows load functions to use some predefined objects.
     IDI_APPLICATION--A constant meaning the standard windows icon.
     IDC_ARROW--A constant meaning the standard arrow pointer.
     (These are defined in the windows.h file)
     Examples:  LoadIcon (NULL, IDI_APPLICATION);
                LoadCursor (NULL, IDC_ARROW);

  .hbrBackground--Use Windows GetStockObject() here to obtain a solid white 
  brush which paints the background color of the window. This is a handle to 
  a brush -- a graphics term referring to the pattern of pixels used to fill
  an area. A brush is an example of a Windows "Graphics Device Interface"
  (GDI) object. Many other GDI objects are used to "paint" on the client
  area of a window, as we'll see later. Under Visual C++ 6.0 the handle to
  the brush obtained by the call to GetStockObject() must be typecast to
  HBRUSH. in other words, the line of code that sets up the hbrBackground
  member of the WNDCLASS structure would have to be:

      wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

  .lpszMenuName=NULL: A menu can be described in the resource script file.
  (See WINAPP2.RC). In WINAPP1, there is no menu, so this is set to null.

  .lpszClassName--The name given to this window class; This will be used when 
  we create a window based on the class.

  After registering the new window class, the program must create the window.
  Any number of windows based on this class can be created.

  2. CREATING THE PROGRAM WINDOW

  CreateWindow() creates the program's window & returns a handle (a unique
  ID) value of type HWND (for the window created).

  Arguments to CreateWindow()--
    window class name
    window caption
    window style (Boolean OR of different predefined style masks)
    initial x position in pixels
    initial y position
    initial width
    initial height
    parent window handle (if main window, no parent ==> NULL)
    window menu handle (NULL if no menu or if class menu is to be used)
    program instance handle (passed in from Windows)
    creation parameters (can be used to access extra data, usually NULL)

  The WINAPP1.CPP program creates its window as follows:

  HWND    hWnd ;
  hWnd = CreateWindow ("MyClass", "First Windows App", WS_OVERLAPPEDWINDOW,
         100, 50, 400, 200, NULL, NULL, hInstance, NULL);


  3. SHOW THE WINDOW AND CAUSE ITS CLIENT AREA tO BE UPDATED

    SHOW THE WINDOW:

    ShowWindow (hWnd,nCmdShow); -- Makes the window just created visible on
    the screen.

    -hWnd: Specifies which window to make visible.

    -nCmdShow: Specifies how (normal, minimized, etc.)
               -Set by the Windows environment when the program is started
               -Value is passed in from Windows
               -"Normal" can be overridden by starting the program from the 
                 program manager "File | Run" and select "Run minimized"


     CAUSE THE WINDOW'S CLIENT AREA TO BE UPDATED (PAINTED WITH THE
     BACKGROUND BRUSH):

     UpdateWindow (hWnd);


  4. THE MESSAGE LOOP

  Any time the user interacts with the window, the Windows operating system 
  sends a message to the window's WndProc(). The interaction could be mouse
  movement, a mouse button click, a keyboard key press, a resizing or
  moving operation that causes some area to be exposed (with a resultant
  need to be repainted), or any number of other operations. (Windows sends
  many other messages as well.)

  A message consists of some data placed in a block of memory:

  A C or C++ structure--

    typedef struct tagMSG
    {
    HWND    hwnd;     /* handle of target window */
    UINT    message;  /* message ID value--always a WM_* constant */
    WPARAM  wParam;   /* a double word of data passed in the message */
    LPARAM  lParam;   /* a second double word of data in the message */
    DWORD   time;     /* time in msec message was sent */
    POINT   pt;       /* mouse cursor (x,y) position when sent */
    }  MSG;

    where:

    typedef struct tagPoint
    {
    int   nx;
    int   ny;
    }  POINT;

  Windows fills in all this data before message is sent.

  A Windows program must continually check for incoming messages.
  This is done by using a small program loop called the message loop.
  One possibility: Use GetMessage()

  GetMessage() function--

    Reads the next message from the application's message queue and fills
    the fields of a variable of type MSG.

  We want put our call to GetMessage() in a loop that continually retrieves
  the message:

  while (GetMessage(&msg, NULL, 0, 0))
       { ... }

   -If Windows has sent the program a message, GetMessage() puts all the
    message data into the structure pointed to by msg.

  BOOL GetMessage (lpMsg, hWnd, uFirstMsg, uLastMsg);

    lpMsg--Address of the structure where Windows should put the message data.

    hWnd--Handle of the window from which the message was sent (NULL ==>
    receive messages from all windows belonging to the application that made
    the call to GetMessage()).

    Last 2 parameters--Specify a range of message values to be retrieved
    (0 means all possible messages).

  If there are no messages, Windows retains control of the system and does 
   other stuff (with other programs) until a message to the program is 
   generated. Only then does GetMessage() return to your program. This is the 
   key to cooperative multitasking!

  For all messages other than WM_QUIT (generated by the WndProc(), as we'll
  see, after user actions like a double click on System menu button or <Alt>-<F4>
  keyboard combination), TRUE (non-zero) is returned to the application's
  message loop and the while loop continues.

  GetMessage() returns 0 if the message is a WM_QUIT. This causes the while
  loop to exit. Then the return(msg.wParam) returns control to Windows,
  effectively terminating the application.

  All well-behaved Windows applications must have a message loop!

  GetMessage() is one of the few functions that returns control to Windows,
  thus letting other applications run. Under Windows 3.xx cooperative
  multitasking, if a program never yields control to Windows, no other
  programs are allowed to run--which is very bad! Under Windows 95 and NT, this 
  is not true, since there is pre-emptive multitasking. Even so, it is not a
  good idea to let your program be a "hog."

  The following figure shows how the main message loop, implemented with
  GetMessage(), works.



  MESSAGE PROCESSING--Typically the main message loop is implemented as
  follows (This is done in the WINAPP1.CPP program):

    while (GetMessage(&msg, NULL, 0, 0))   /* message loop */
    {
        TranslateMessage(&msg);   /* translate keyboard messages */
        DispatchMessage(&msg);    /* send message to WndProc() */
    }
    return (msg.wParam);


  TranslateMessage (&msg)--

   This function "cooks" keyboard input and converts raw key codes to ANSI
    codes that can be used by the program.

   Actually it converts WM_KEYDOWN messages (sent when any key goes down)
    to WM_CHAR messages that recognize specific keys and key combinations,
    e.g., <Shift>-<anything>.

   These WM_CHAR messages are processed in a well-defined way by the
    default Window procedure.

   This provides a keyboard alternative to using the mouse to select things.
   Most Windows programs use TranslateMessage() in their message loops.

  DispatchMessage (&msg)--

   Since message processing will be done in the function WndProc(), the
   program must tell Windows to pass any message data to that function.

   This is done with DispatchMessage(&msg), which sends the message on to
   Windows. Windows, in turn, forwards it to the function specified in the
   lpfnWndProc member of the WNDCLASS structure--the WndProc().

   Data is sent along in the message structure that was filled in by
   GetMessage().

   This happens each time a message is received. Messages are continuously
   retrieved from the program's queue and dispatched to its WndProc().


B. THE WINDOW PROCEDURE--

  This is a "callback" function (named so because it is called by Windows).
  It contains a big switch/case statement that looks at the message ID of the
  current message and acts appropriately on any "interesting" messages.

  LRESULT CALLBACK WndProc (HWND hWnd, UINT wMessage,
                            WPARAM wParam, LPARAM lParam)

  Parameters--The same as the first four fields of the MSG structure--
     -the window the message is associated with.
     -the message ID--what the message is.
     -any data associated with the message (wParam & lParam message
      parameters): this data is specific for each type of message.
        wParam--"word" parameter; (16 bits under Win16).
        lParam--long parameter (32 bits).
        [This changed under Win32 -- Now both are 32 bits.]

  The following figure shows how Windows and the application interact in the
  processing of messages.



The WndProc() in the WINAPP1.CPP example looks for the following message:

  WM_DESTROY--Generated when the user double clicks on the system menu,
  chooses "Close" from the system menu, or presses <ALT>-<F4>. This message means
  that Windows is in the process of removing the window from the screen.

  Any other messages are handled by DefWindowProc(), the default Window
  procedure.

  Other important messages are:

    WM_COMMAND--User interacted with a menu item, wParam=Menu item ID).

    WM_LBUTTONDOWN--User pressed the left mouse button (lParam contains the
    x,y coordinates).

    WM_RBUTTONDOWN--User pressed the right mouse button.

    WM_CHAR--User pressed a key or key combination that corresponds to a valid
    ANSI code (wParam contains the ANSI code).

  It is essential that our programs trap the WM_DESTROY message. If not the
  GetMessage() loop will carry on forever, even though the window is gone!

  WINAPP1's Response to WM_DESTROY--

   -The program calls PostQuitMessage(0).

   -PostQuitMessage() causes Windows to send a WM_QUIT message to the
    program's message queue. Recall that when this is encountered,
    GetMessage() returns a 0 (the only message for which it does that).

   -This causes the program to exit WinMain()'s while loop.

   -It then hits the return(msg.wParam) instruction.

   -The value returned is that accompanying the WM_QUIT message (0), and
    control goes to Windows (doesn't use the 0), which releases all memory used
    by the program.

  The following figure illustrates how the WM_DESTROY message should be
  handled.



  If it's some other message, the program calls DefWindowProc()--the default
  procedure. This preforms default actions for the message received; e.g.,
  pressing the left mouse button while moving the cursor is over title bar will
  result in default dragging action. (Movement, repainting are all done by Windows
  default function.) The DefWindowProc() function is in Windows.


Our WINAPP1.CPP program code overrides default window procedure action for
the WM_DESTROY message.


USING MICROSOFT DEVELOPER STUDIO 97 TO CREATE A Windows Win32 VISUAL C++
APPLICATION

As you will see, the process of creating a Win32 API application is very similar to 
creating a Windows Console application, discussed earlier. Detailed steps are given 
below.

Startup:

  1. From Windows (NT or 95) click 'Start' on Task Bar

  2. Select 'Programs | Microsoft Visual C++ | Microsoft Visual C++5.0'

  3. Close the "Tip of the Day" box (if it comes up)

  You should see the window containing three subwindows:
    left: the Project Workspace Window
    right: the Editor Window
    bottom: the Output Window

Creating the Project:

  1. Select 'File | New'

  2. Click on 'Projects' Tab (if not chosen already)

  3. Select 'Win32 Application'

  4. Name the project (e.g. winapp1)
       The system will use the directory stated in 'Location' as the
       parent directory for the project directory that it will create.
       You can change this parent directory as you like. Be sure to
       use a hard disk drive. The project directory will have the name you
       give in this step, and all files created by Developer Studio will be
       in that directory.

  5. Click 'OK'
     Note there are now three tabs in the project workspace window:
       ClassView -- to look at the classes in a C++ program
       FileView -- to look at the various files in the project
       InfoView -- to access on-line help

  [Under Visual C++ 6.0, there is an additional step after step 3. After you
  choose 'Win32 Application', a dialog box comes up that asks you what kind
  of windows application you would like to create: An Empty Project, A
  Simple Win32 application, or A Typical "Hello World: application. For
  the kinds of applications we'll be creating in this class, select the
  first choice (An Empty Project), and then click the "Finish" button.]

Adding a new C++ source file to the project:

  1. Select 'File | New' and make sure that the 'Files' tab is selected. 
     
  2. Choose 'C++ Source File', enter the name you want to give the file
     (without extension) in the 'File name' edit control, and click the 'OK'
     button. This will cause an empty document to appear in the Editor Window.
     In addition, if you now select the 'FileView' tab in the Project Workspace
     Window and expand the workspace files listing, you will see that a .cpp
     file has been added to the project.

  3. Type in the source code. Or easier yet, copy and paste source code from
     the example programs given on the course web pages and edit it to do
     whatever it is that your application is supposed to do.

[Inserting source files that have already been created into the project:

  1. Use any editor to create the .cpp (or .c), [and also any .h, .rc files 
     used]. Save them in the directory that contains your other project files--
     i.e., the directory that was created above by Developer Studio. If the 
     source files already exist, just copy them into the project's directory.

  2. From the menu select 'Project | Add to Project | Files'

  3. Make sure the 'Look In' box contains the directory with your files.
     The files should appear in the larger box below.

  4. Select the .c or .cpp file(s) (e.g., winapp1.cpp)
     and click 'OK'. If you now click on the FileView
     tab in the Workspace Window, you will see that the file has been
     included in the project. To see it in the Editor Window, click on
     the file name from the FileView window.]

Building the Project:

  1. Select 'Build | Build project_name.exe' (e.g., Build example.exe)
       The Project will be compiled/linked.
       Messages (errors) will appear in the Output Window.
     (A shortcut keystoke that will do the same thing is F7.)

Running the Application:

  Select 'Build | Execute project_name.exe' (e.g., Execute example.exe)
     The application will execute.
     (A keyboard shortcut is <Ctrl><F5>.)
     (Or, if an red exclamation point (!) icon appears on a toolbar, click it.) 

Cleanup:

  The entire project (LOTS of files--over 3 Megabytes worth!) will be
  in the directory you chose when you created the project on C drive.
  The most important files in this directory are the .dsp (Designer Studio
  Project) and .dsw (Designer Studio Workspace) files. You should copy
  them (along with all your source files) to your diskette. In future
  sessions, the project can be brought into Designer Studio by copying the
  .dsp, .dsw, and source files to the C drive (in a temporary directory)
  and selecting 'File | OpenWorkspace'. Open the temporary directory,
  select 'Files of Type:' 'Project' from the lower list box, and double
  click on your .dsp or .dsw file in the upper box. You can then rebuild
  the project.

  After building a project, the project directory will will have a
  DEBUG subdirectory. The executable (.exe) will be in that DEBUG
  subdirectory. You should also copy it to your diskette.

  After you are sure that you have all your source files, your .dsp and
  .dsw files, and your .exe file on your diskette, delete the project
  directory from the hard drive to clean it up.

Exiting Developer Studio:

  Select 'File | Exit'.