Retro video games delivered to your door every month!
Click above to get retro games delivered to your door ever month!
Anatomy of a Simple Win32 Application http://www.X-Hacker.org
 

Simple Win32 Application

Robert B. Hess
Microsoft Corporation

Created: October 17, 1994

Robert Hess is a Software Design Engineer for Microsoft, working in the Developer Relations group. He has worked for Microsoft for over six years as a developer for various Windows platforms.

Click to open or copy the files in the Generic sample application for this technical article.

Abstract

This article reviews some of the aspects of the Generic sample application that illustrate a well-behaved application written for the Win32® environment.

Introduction

Generic is a sample application that attempts to illustrate the "minimum" functionality that should be expected of a well-behaved application developed for the Win32® environment. Other than that, this application has virtually no actual functionality of its own. This makes it easier for you to use Generic as a starting point for your own application development or for investigating the Win32 Application Programming Interface (API).

This document will attempt to provide a quick overview of the important aspects of the Generic application, focusing on the areas that are new or different from 16-bit Windows®.

This article is not a tutorial. I will assume that the reader is already familiar with the introductory aspects of Windows-based programming.

Figure 1. The Generic sample application

A "Generic" History

The Windows Software Development Kit (SDK) has included a Generic sample application for quite some time. When the Win32 SDK was first being developed, I noticed that this application was quite sad indeed. While it did bring up a window on the screen, it did very little more than this. The program didn't even have an icon associated with it—it was a bare-bones Windows-based application rather than a generic one. I also noticed that many of the other sample applications that we were presenting also lacked many of the aspects of programming that we are constantly encouraging developers to implement in their Windows-based applications.

For the Win32 SDK, I decided that I would rewrite generic as the "bar" that virtually every Microsoft® Windows-based application should at least be able to clear. By definition, Generic would have no actual functionality of its own, thereby not requiring you to remove existing code in order to modify it for your own purposes. However, at the same time, Generic would attempt to illustrate as many aspects of Windows programming as possible that should be included in all Windows-based programs.

The Windows 95 SDK includes a version of my Generic sample. In fact, many of the sample applications within the Windows 95 SDK are based on this version. Unfortunately, all of these samples are currently using a slightly lobotomized version of Generic that doesn't fully illustrate how an application that is compatible with Windows 95 should use the Win32 API. This article introduces the "new and improved" version of Generic that attempts to present a minimum level of functionality that all Windows-based applications should be able to meet, as well as being a totally well-behaved citizen of the Windows 95 environment.

Because the Windows 95 operating system is still under development at this time, there may be additions and changes to what should be expected of an application written for this environment. As these changes are added to the system, I will be updating the sources associated with this article to illustrate these aspects. Please check the updates to this article to determine if there are changes that you need to be aware of. Due to changes that have been made in the Windows 95 operating system since the M6 release of the beta Windows 95 SDK, the version of Generic that is presented here is not fully compatible with the M6 release.

Topics Discussed in This Document

Registering Both Large and Small icons

Windows 95 introduces the use of smaller icons in some of its user interface features, most notably in the title bar to replace the System menu icon. Unfortunately, simply compressing the already registered 32x32 icon for this icon can result in a rather ugly image, and because there was no other way to alert the system as to exactly which icon to use for this, a new function, RegisterClassEx, had to be brought on board. This function is essentially the same as the old RegisterClass call, except that the structure you pass in has two additional elements: cbSize and hIconSm. The cbSize value keeps track of any additional changes that might be needed for this structure and allows the system to "do the right thing," based on which structure is being used. The hIconSm value passes in the handle to the small icon.

In my sample code, I use LoadIcon to get the small icon handle. The icon resource I load contains only a small icon (16x16), so I don't have to worry about Windows pulling out the 32x32 icon instead. The new LoadIconEx function, which will allow me to specify the desired icon resolution, should be available any day now in Windows 95. When this is in, I will be able to include both the 32x32 and the 16x16 icon in a single icon resource.

Figure 2. 32x32 icon

Figure 3. 32x32 icon shrunk to 16x16

Figure 4. 16x16 icon

Locating a "Previous" Instance

In both Windows NT and Windows 95, the hPrevInstance parameter that is passed to the WinMain function will always be NULL. This is because it is no longer necessary (or appropriate) to perform things such as RegisterClass only during the execution of the first instance of an application.

This poses a problem. If you want only a single instance of your application to run at one time, you now need to work a little harder in accomplish this. In my sample code, I illustrate a common and recommended method of doing this, which is to use the FindWindow function to locate another window of a specified class.

hwnd = FindWindow (szAppName, NULL);
if (hwnd) {
   // We found another version of ourself. Let's defer to it:
   if (IsIconic(hwnd)) {
      ShowWindow(hwnd, SW_RESTORE);
   }
   SetForegroundWindow (hwnd);
   // If this app actually had any functionality, we would
   // also want to communicate any action that our "twin"
   // should now perform based on how the user tried to
   // execute us.
   return FALSE;
}

A Menu Bar That Conforms to the UI Guidelines

While there is no ironclad definition for a menu bar that all applications should use, there are some general guidelines on the placement and layout of menu items. For example, it is extremely bad practice to include top-level menu items that don't have pop-ups attached (when the user clicks the menu name, it carries out some action). I see this very often in sample applications, and there isn't any excuse for it.

In Generic, I have followed the general guidelines for the layout of a menu bar, and simply disabled those that don't apply to this sample application. If you were going to turn this into a test-bed application, you might want to add a Test menu between the Edit and Help menus. You could then list multiple menu items that indicate various tests that you have coded.

If you need to implement any of the disabled menu items, simply remove the GRAYED attribute from its definition in the resource file, and then add code to the specific case statement in the code.

The suggested layout for the Help menu has changed for Windows 95. You'll notice that I actually define two separate menus in my resource, and at run time I determine the correct one to assign to the class I am registering. There are other ways to do this, but this method is relatively simple and easy to see.

GENERIC MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&New",                        IDM_NEW, GRAYED
        MENUITEM "&Open...",                    IDM_OPEN, GRAYED
        MENUITEM "&Save",                       IDM_SAVE, GRAYED
        MENUITEM "Save &As...",                 IDM_SAVEAS, GRAYED
        MENUITEM SEPARATOR
        MENUITEM "&Print...",                   IDM_PRINT, GRAYED
        MENUITEM "P&rint Setup...",             IDM_PRINTSETUP, GRAYED
        MENUITEM SEPARATOR
        MENUITEM "E&xit",                       IDM_EXIT
    END
    POPUP "&Edit"
    BEGIN
        MENUITEM "&Undo\tCtrl+Z",               IDM_UNDO, GRAYED
        MENUITEM SEPARATOR
        MENUITEM "Cu&t\tCtrl+X",                IDM_CUT, GRAYED
        MENUITEM "&Copy\tCtrl+C",               IDM_COPY, GRAYED
        MENUITEM "&Paste\tCtrl+V",              IDM_PASTE, GRAYED
        MENUITEM "Paste &Link",                 IDM_LINK, GRAYED
        MENUITEM SEPARATOR
        MENUITEM "Lin&ks...",                   IDM_LINKS, GRAYED
    END
    POPUP "&Help", HELP
    BEGIN
        MENUITEM "&Help Topics...",             IDM_HELPTOPICS
        MENUITEM SEPARATOR
        MENUITEM "&About Generic...",           IDM_ABOUT
    END
END

Context Menu on Right-Click in the Client Area

Windows 95 is placing a high level of importance on the use of the right mouse button to bring up context-sensitive menus. Since Generic doesn't have anything to be context-aware of, I am merely bringing up the pop-up from the Help menu. You can easily replace this with code that will determine what sort of context menu makes sense to bring up, and do that instead. In most cases, you would have alternate pop-up menus defined in your resource file that you would select from, or you would build the menus on the fly.

case WM_RBUTTONDOWN: // Right-click in window's client area...
   pnt.x = LOWORD(lParam);
   pnt.y = HIWORD(lParam);
   ClientToScreen(hWnd, (LPPOINT) &pnt);

   // This is where you would determine the appropriate "context"
   // menu to bring up. Since this app has no real functionality,
   // we will just bring up the Help menu:
   hMenu = GetSubMenu (GetMenu (hWnd), 2);
   if (hMenu) {
      TrackPopupMenu (hMenu, 0, pnt.x, pnt.y, 0, hWnd, NULL);
   } else {
      // Couldn't find the menu...
      MessageBeep(0);
   }
   break;

Version Information in the Resource

The File Manager in Windows NT and the property sheets in Windows 95 will allow the user to browse certain strings in the application's resource to look at version information. Adding this resource type to your application allows your application to expose some useful information to the user.

The format of the information being stored in the resource is not quite as simple as ordinary string resources, but you should be able to get a good idea about the layout by simply looking at the sources.

1 VERSIONINFO
FILEVERSION     3,5,0,0
PRODUCTVERSION  3,5,0,0
FILEOS          VOS__WINDOWS32
FILETYPE        VFT_APP
BEGIN
   BLOCK "StringFileInfo"
   BEGIN
      BLOCK "040904E4"
      BEGIN
         VALUE "FileDescription", "Generic Example Application\0"
         VALUE "LegalCopyright",  "Copyright \251 Microsoft Corp. 1990 - 1994\0"
         VALUE "Comments",        "Written by: Robert B. Hess\0"
         VALUE "CompanyName",     "Microsoft Corporation\0"
         VALUE "FileVersion",     "3.5\0"
         VALUE "LegalTrademarks", "Microsoft\256 is a registered trademark of Microsoft Corporation. Windows(tm) is a trademark of Microsoft Corporation\0"
         VALUE "ProductName",     "Generic\0"
         VALUE "ProductVersion",  "3.5\0"
      END
   END
   BLOCK "VarFileInfo"
   BEGIN
      VALUE "Translation", 0x409, 1252
   END
END

Figure 5. Version information

An About Box

About boxes are extremely easy to implement, but it is amazing how many sample applications don't include one. Although it would have been very easy for me to simply call MessageBox, I chose to almost double the amount of code in Generic to bring up an About box that could be used in a retail application. Since virtually all of this special functionality is coming out of the resource file, it is very easy to modify the look of this dialog box to fit your needs.

Figure 6. The Generic About box

Setting the Font to Non-Bold in a Dialog Box

One problem that people always complain about is that dialog boxes always use a bold font. Therefore, I decided to show two things in my About box: how to switch to a more normal font, and how to set two separate fonts in the same dialog (which isn't any harder). All it takes is to properly create a font handle using the CreateFont function and then use WM_SETFONT to tell the control which the new font to use. When the dialog exits, you need to clean up the fonts, of course.

hfontDlg = CreateFont(14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, VARIABLE_PITCH | FF_SWISS, "");
hFinePrint = CreateFont(11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, VARIABLE_PITCH | FF_SWISS, "");
...
SendMessage ( GetDlgItem (hDlg, i), WM_SETFONT, (UINT)((i==IDC_TRADEMARK) ? hFinePrint : hfontDlg),TRUE);

Populating the About Box with the Version Information From the Resource

I use the version information that is stored in the resource to populate static text fields in my About box dialog for two reasons: to help remind you to update this information, and to prevent you from forgetting to change similar information in two separate locations. I simply assigned the default text of these static text controls to be the name of the version string that should be inserted into the static controls at run time. My About box dialog procedure does the rest.

While it is possible to add multilingual version text to your resource, at the present time, neither the resource nor the dialog procedure are doing anything specifically along these lines. I leave that up to you to do on your own.

// Walk through the dialog items that we want to replace:
for (i = DLG_VERFIRST; i <= DLG_VERLAST; i++) {
   GetDlgItemText(hDlg, i, szResult, sizeof(szResult));
   szGetName[wRootLen] = (char)0;
   lstrcat (szGetName, szResult);
   uVersionLen = 0;
   lpVersion = NULL;
   bRetCode =  VerQueryValue((LPVOID)lpstrVffInfo, (LPSTR)szGetName, (LPVOID)&lpVersion, (UINT *)&uVersionLen);
   if ( bRetCode && uVersionLen && lpVersion) {
      // Replace dialog item text with version info
      lstrcpy(szResult, lpVersion);
      SetDlgItemText(hDlg, i, szResult);
   }
} // for (i = DLG_VERFIRST; i <= DLG_VERLAST; i++)

Centering a Dialog Over the Application Window

I've noticed some professional retail applications that bring up dialog boxes that are skewed in relationship to the parent application, or worse yet, clipped by the edge of the screen, sometimes to the point that the OK button is not even visible

It is fairly easy to reposition the dialog boxes so that they maintain a relationship with the parent application and aren't clipped by the screen. I use the following code in virtually all of my applications. I find that it adds that little touch that isn't necessarily noticed by the user, but would be noticed if it hadn't been there.

Windows 95 also adds the task bar, which could occlude the window or dialog, so I have modified this code to take that into account when positioning the window.

// Get the height and width of the child window.
GetWindowRect (hwndChild, &rChild);
wChild = rChild.right - rChild.left;
hChild = rChild.bottom - rChild.top;

// Get the height and width of the parent window.
GetWindowRect (hwndParent, &rParent);
wParent = rParent.right - rParent.left;
hParent = rParent.bottom - rParent.top;

// Get the limits of the "work area".
bResult = SystemParametersInfo( SPI_GETWORKAREA, sizeof(RECT), &rWorkArea, 0);
if (!bResult) {
   rWorkArea.left = rWorkArea.top = 0;
   rWorkArea.right = GetSystemMetrics(SM_CXSCREEN);
   rWorkArea.bottom = GetSystemMetrics(SM_CYSCREEN);
}

// Calculate new X position, then adjust for work area.
xNew = rParent.left + ((wParent - wChild) /2);
if (xNew < rWorkArea.left) {
   xNew = rWorkArea.left;
} else if ((xNew+wChild) > rWorkArea.right) {
   xNew = rWorkArea.right - wChild;
}

// Calculate new Y position, then adjust for work area.
yNew = rParent.top  + ((hParent - hChild) /2);
if (yNew < rWorkArea.top) {
   yNew = rWorkArea.top;
} else if ((yNew+hChild) > rWorkArea.bottom) {
   yNew = rWorkArea.bottom - hChild;
}

SetWindowPos (hwndChild, NULL, xNew, yNew, 0, 0, SWP_NOSIZE | SWP_NOZORDER);

Version Checking to Detect Win32s, Windows NT, or Windows 95

Since applications written for Win32 can now run on three separate operating systems (Windows NT, Windows 3.1 with Win32s, and Windows 95), it is sometimes very important for applications to determine which system they are running on, so they can decide which features and functionality to expose. It is really quite simple to do this, but many applications still don't seem to do it quite right. For a 32-bit application, the code you need is shown below:

dwVersion = GetVersion();
if (dwVersion < 0x80000000) {
   // Windows NT
} else if (LOBYTE(LOWORD(dwVersion))<4) {
   // Win32s
} else {
   // Windows 95
}

I'm using this code to populate a string in my About box that reports the version of Windows this program is being run on.

Properly Populated WinHelp Menu

One of the menus that seems to confuse a lot of people is the Help menu. I will agree that there hasn't been enough information that specifically illustrates how this should be done, but after reading through the UI guide and looking at a number of applications, I am presenting a layout in this application that appears to be fairly common. Personally, I find the Help On Help item kind of awkward, but it is in the Windows 3.1 UI guidelines as well as in many applications, so I include it here to illustrate its implementation.

It is also important to note that the Windows 95 Help menu has a slightly different recommended layout, based on some of the changes in displaying WinHelp to the user in Windows 95. As mentioned previously, I am determining which menu to associate with the application at run time as a quick and easy way to switch the layouts.

Sample layout for the Help menu in Windows 3.x:

POPUP "&Help"
   BEGIN
      MENUITEM "&Contents",                   IDM_HELPCONTENTS, HELP
      MENUITEM "&Search for Help On...",      IDM_HELPSEARCH, HELP
      MENUITEM "&How to Use Help",            IDM_HELPHELP, HELP
      MENUITEM SEPARATOR
      MENUITEM "&About Generic...",           IDM_ABOUT
   END

Sample layout for the Help menu in Windows 95:

POPUP "&Help"
   BEGIN
      MENUITEM "&Help Topics...",             IDM_HELPTOPICS
      MENUITEM SEPARATOR
      MENUITEM "&About Generic...",           IDM_ABOUT
   END

Proper Launching of WinHelp

In addition to a lack of About dialogs in sample applications, I also notice a severe lack of any WinHelp support. While a quick test application that you write may not warrant the time and energy required to prepare a Help file, I feel that any sample application that is being distributed for others to look at and utilize should definitely include a Help file. How many times have you received a set of sample applications, quickly compiled all of them, then started running them to see what they demonstrated? How many times did you look at one of these applications, slightly stunned because you didn't have a clue as to what you were supposed to do? Several (OK, many) of our own sample applications suffer from this, and there really isn't any good excuse for it.

One of the possible reasons is the lack of good example code that illustrates exactly how you should launch WinHelp in order to make it useful. Most people probably don't tackle that aspect until later in their development cycle. Well, that excuse is now useless:

case IDM_HELPTOPICS: // "Help Topics...": Only called on Windows 95
   bGotHelp = WinHelp (hWnd, "GENERIC.HLP", HELP_FINDER,(DWORD)0);
   break;
case IDM_HELPCONTENTS: // "Contents": Not called on Windows 95
   bGotHelp = WinHelp (hWnd, "GENERIC.HLP", HELP_CONTENTS,(DWORD)0);
break;
case IDM_HELPSEARCH: // "Search for Help On...": Not called on Windows 95
   bGotHelp = WinHelp(hWnd, "GENERIC.HLP", HELP_PARTIALKEY, (DWORD)(LPSTR)"");
   break;
case IDM_HELPHELP: // "How to Use Help": Not called on Windows 95
   bGotHelp = WinHelp(hWnd, (LPSTR)NULL, HELP_HELPONHELP, 0)) {
   break;

A WinHelp Source File That Doesn't Require an RTF Word Processor

The other reason that many sample applications don't include WinHelp support is probably because the authoring of a WinHelp file can often be quite daunting. I personally have never seen any good documentation in an SDK or Integrated Development Environment package that properly describes how to author WinHelp files. Add to that the fact that the suggested authoring tool is a word processor that supports RTF files (such as Word for Windows), and the fact that you are basically "tricking" this word processor to format the document for WinHelp, and it isn't very surprising that most developers don't add WinHelp to their sample applications.

Since RTF is a format for describing a rich text document using normal text, it actually is possible to create a WinHelp file with any text editor. In GENERIC.RTF, you will not only find basically human-readable text that creates a reasonable WinHelp file, but I have also tried to add enough comments to give you a quick idea on what is necessary for WinHelp. You might want to track down some RTF documentation if you want to do anything fancier than what I am doing in my sample. The MSDN Library CD-ROM contains full RTF documentation.

I have supplied two different ways to create the help file: The MAKEFILE script creates the Help file using the HC31 help compiler, and MAKEHELP.BAT uses the new help compiler (HCRTF) that comes with the Windows 95 SDK to build a WinHelp file.

A .CNT File for Creating a Proper Index Page Using Windows 95 WinHelp

One of the changes for Windows 95 is a new method of navigating the WinHelp file from the contents page. One thing this adds to the process is a .CNT file that describes the layout of the contents. By including a .CNT file with your .HLP file, Windows 95 will be able to create a contents/index page for you automatically. It then saves this page out as a hidden file so it won't have to recreate it again (this is what happens when the little "flipping page" dialog comes up when you start up a WinHelp file in Windows 95 for the first time). If the timestamp on the .CNT or .HLP file gets altered, Windows 95 will create a new index file.

:Base generic.hlp
:Title Sample Dialog Title
:Index=generic.hlp
1 Introduction=Introduction@generic.hlp
1 Topics
2 First=FIRST_TOPIC@generic.hlp
2 Second=SECOND_TOPIC@generic.hlp
2 Third=THIRD_TOPIC@generic.hlp
2 Fourth=FOURTH_TOPIC@generic.hlp

Refer to the Windows 95 documentation for full information on the format of a .CNT file

Monitoring the Display Change Message to Detect Dynamic Resolution Changes

Plug and Play is a new feature for Windows 95, and it is fairly important for applications to start using it. Since Generic isn't opening up any resources on devices that might go away, there really isn't anything for Generic to do with regards to Plug and Play. However, I have added the code to detect when the user dynamically changes the screen resolution. All that I do is post a MessageBox to report this fact, but you may want to do other things in your application.

case WM_DISPLAYCHANGE:
   szScreen.cx = LOWORD(lParam);
   szScreen.cy = HIWORD(lParam);
   if (fChanged) {
      // The display *has* changed. szScreen reflects the
      // new size.
      MessageBox (GetFocus(), "Display Changed", szAppName, 0);
   } else {
      // The display *is* changing. szScreen reflects the
      // original size.
      MessageBeep(0);
   }
   break;

A "Normal" nmake Script (MAKEFILE) That is Human-Readable

Despite the landslide penetration of Visual C++ into the development market, I think it is important to include fairly simple nmake scripts so that people know which switches and such are being set. I did take a little extra time and tried to make a nmake script that is flexible enough that you can build any relatively simple project by simply making a few changes.

I hope I added enough comments so that you can see what is going on.

A Visual C++ nmake Script (GENERIC.MAK) That Can Be Used to Build This Project Using Visual C++ 2.0

And because of the landslide penetration of Visual C++, I am also including an nmake script created by Visual C++ 2.0. One drawback is that Visual C++ doesn't understand how to build WinHelp files, so you will have to do this manually if you use this script file.

If you are using a development environment other than Visual C++ 2.0 that is capable of automatically generating make scripts for you, all you should need to do is start up a new project and add GENERIC.C and GENERIC.DEF to it. You will also want to add VERSION.LIB to the list of standard libraries that you link to.

Compatibility of Resulting Executable With Win32s, Windows NT, and Windows 95

Last, but probably most important, is the fact that the Generic application is fully compatible with Win32s, Windows NT, and Windows 95.

Summary

As I indicated at the beginning of this article, Generic is not intended to have any real functionality of its own. Instead, it is meant to provide a very minimal skeleton that you can use for building your own sample applications. Because of this, there are a lot of things that Generic doesn't illustrate.

For the purpose of illustration, all of the code for Generic is included in a single source code file. While this does make it easier to understand what is going on in this application, it is not a recommended method to follow for your own applications. It is more appropriate to modularize your code so that separate source code files contain code for specific functionality. Furthermore, Generic does not illustrate the usage of any of the common dialogs or any of the new common controls that have been added to Windows 95. These and other aspects of programming for the Windows environment are best left up to other sample applications, hopefully coming soon to a source code library near you!