MFC TN012--Using MFC with Windows version 3.1 Robustness Features

Created: March 25, 1992

Abstract

This technical note describes some new features found in Microsoft Windows version 3.1 and describes their use within the MFC library.

The Microsoft Foundation Class (MFC) library provides a full-featured set of C++ object classes for the MicrosoftÒ WindowsÔ graphical environment. It includes classes that directly support application development for Windows as well as general-purpose classes for collections, files, persistent storage, exceptions, diagnostics, memory management, strings, and time. Each MFC technical note describes a feature of MFC using code fragments and examples.

Overview

MicrosoftÒ WindowsÔ version 3.1 is a major improvement over Windows version 3.0 in the area of robust application development. Windows version 3.1 includes a number of new features that enhance the reliability of a Windows-based application. This technical note describes the use of these features within the MFC library.

Windows version 3.1 Debug Kernel

Testing your MFC application with the debug system executables is probably the best thing you can do to make sure your applications are robust and reliable. The debugging versions of the system executables perform all sorts of useful error checking for you, informing you of any problems that arise with debug output messages.

The best way to use the debug system is with two machines: a machine for testing and debugging that has the debug system installed, and a machine for development. If you put a monochrome adapter card in your debug machine, Microsoft CodeViewÒ and debug system output can be directed to the monochrome monitor.

But not everyone can afford to buy a second machine just for debugging. One drawback of installing the debug system on your development machine is that it runs somewhat slower than the standard Windows system, so it slows down your compiler, editor, and other debugging tools.

A useful trick for single-machine debugging is to place copies of the system and debug binaries and symbols in a separate directory, and have batch files that copy the appropriate files to your Windows system directory. This way you can exit Windows and switch back and forth quickly between debug and nondebug. The Microsoft Windows version 3.1 Software Development Kit (SDK) installation program will set this up for you; then you can switch between debug and nondebug version of Windows with the D2N.BAT and N2D.BAT batch files.

If you aren't running with a debugger or a debug terminal, you should run the DBWIN application so you can see the error and warning messages produced by the debug system. This application is included with the SDK.

Here are some common programming errors that appear more frequently than they should in shipped applications for Windows. Many of these problems can cause random system UAEs and other problems under Windows version 3.0. The debug system binaries will help you track down these kinds of problems very quickly:

Passing invalid parameters of all shapes and sizes.

Accessing nonexistent window words. In version 3.0, a SetWindowWord or SetWindowLong call past the end of the allocated window words (as defined with RegisterClass) would trash internal window manager data structures!

Using handles after they've been deleted or destroyed.

Using a device context (DC) after it has been released.

Deleting graphics device interface (GDI) objects before they're deselected from a DC.

Forgetting to delete GDI bitmaps, brushes, pens, or other GDI or USER objects when an application terminates.

Writing past the end of an allocated memory block.

Reading or writing through a memory pointer after it has been freed.

Forgetting to export window procedures and other callbacks.

Forgetting to use MakeProcInstance with dialog procs and other callbacks (alternatively, you can use the Microsoft C/C++ version 7.0 feature of marking these functions as EXPORT in their declaration).

Shipping an application without running it with the debugging KERNEL, USER, and GDI.

MFC Diagnostics

In addition, the Microsoft Foundation Classes ship with a set of robustness features that are compiled and linked only in the debug build of the library (those library variants ending with a d.) Use of these features in the applications you write and in the classes you design will greatly improve both the run-time and compile-time error trapping of your application. These functions are outlined below, but all are documented in the Class Libraries Reference manual.

Every class in MFC includes a class invariant, AssertValid. This member function is called to verify the "sanity" of a C++ object. You use this function whenever objects (or pointers or references to objects) are passed as parameters. The macro ASSERT_VALID(pObject) is supplied so that these calls are not compiled for retail builds. Your class's implementation of this function should first call the base class AssertValid explicitly before doing any derived class-specific validation.

Parameter validation is another important feature. Aside from validating objects, it is also important to validate buffers and string constants. The global function AfxIsValidAddress should be used to verify the validity of a buffer/length pair. The Class Libraries Reference provides you with specific information on this API.

Every class in MFC implements a Dump member function, which permits you to view the state of an object in an ASCII format. This function can be called from CodeView or placed within #ifdef _DEBUG /#endif portions of your code. You should supply a Dump member for classes that you implement. As with AssertValid, you should first explicitly call your base class Dump member function. The output of Dump is routed to the "afxDump" CDumpContext, which by default goes to the CodeView output window or to your debug terminal. You can also use the DBWIN program to view the output of afxDump. The source file DUMPINIT.CPP in the Sample Code, MFC Samples, SRC area on this disc includes information on how to route afxDump to another destination.

TRACE is a macro that behaves much like printf, but it routes output to the afxDump location. You should use TRACE statements to indicate tricky or exceptional places in your code. As with other robustness features, TRACE is only meaningful in the debug library and has no effect in the retail build. The MFC library includes a number of built-in TRACE statements for tracking the flow of messages. Please see "Windows Debugging and Trace Options" (TN007) for more information on the debug trace information.

ASSERT is a run-time check for the validity of a statement. You should use ASSERTs liberally throughout your program. Any place you have a comment to the effect:

/* lpStr should be NULL at this point */

you should replace that with a run-time assert:

ASSERT(lpStr == NULL);

The compiler cannot understand English, but it can execute code! ASSERT statements have no effect in retails builds. If you need the side effects of an ASSERT to occur in the retail build as well, then use the VERIFY macro.

MFC also includes an extensive diagnostic memory allocator. You use the diagnostic memory allocator to make sure that you free all memory resources before your program exits. The diagnostic allocator will track the source file and line number of an allocation, so if you use the CMemoryState::DumpAllObjectsSince API you can locate any remaining allocations. A good place to use this API is in the ExitInstance member function of CWinApp (which you can override, but be sure to call the base class ExitInstance). Additionally, if you have a rather tricky piece of code that does lots of allocations and deallocations, you can use the CMemoryState::Checkpoint and CMemoryState::Difference APIs to be sure to leave the world in a steady state after executing such code.

Windows version 3.1 STRICT Type Checking

STRICT type checking is an option available with the new WINDOWS.H header file. Before a discussion of the use of STRICT, a brief description of C++ typesafe linkage is in order.

In C++ you are permitted to have many functions with the same name, as long as these functions have different formal parameter lists. In order to have unique link symbols, the C++ compiler will "decorate" these names using an algorithm that encodes information about a function such as the name, number and type of formal parameters, calling convention, and so on. This newly generated name is used as the external link symbol for the function. This is known as typesafe linkage and is a big benefit of C++. This name decoration does not apply to functions within an extern "C" block and is why all APIs in WINDOWS.H are in such a block.

STRICT typechecking in WINDOWS.H attempts to imitate C++ type safety for C by using distinct types to represent all the different HANDLES in Windows. For example, STRICT prevents you from mistakenly passing an HPEN to a routine expecting an HBITMAP. Since the Windows APIs are all within extern "C" { } blocks, they are not decorated in the manner described above. STRICT plays tricks with the C compiler by changing the types of the various Windows-based typedefs to make them unique (specifically, it uses different pointer types to represent HANDLEs, which cannot be freely converted without an explicit cast).

As you can see, if you have STRICT typechecking enabled in one file but not in another, the C++ compiler will generate different external link symbols for a single function. This will result in link-time errors. Therefore, it is recommended that you use STRICT typechecking only for C modules (those that end in .C). Additionally, STRICT is a compile-time-only option, so once you successfully compile your code the benefits of STRICT are completely realized.

MFC accomplishes all of STRICT static typechecking, plus a whole lot more, by using the C++ language. Therefore, STRICT is disabled by default. If you wish to use STRICT, you will need to recompile the MFC libraries that you regularly use (because of typesafe linkage, the external link symbols are different between STRICT and non-STRICT).

The following are guidelines for using STRICT with C/C++/MFC.

If you have existing Windows version 3.0 C code and you wish to keep it as C code, you can work to make it STRICT-compliant or not; that option is yours. Be sure to use C++ conventions for your exported header file, such as putting extern "C" { } around your C APIs if you intend to use the header file within any C++ code. If you have existing Windows version 3.1 STRICT C code, then moving it to C++/MFC eliminates the need for STRICT.

For any C code that you have, the use of STRICT is optional and encouraged. For C++ code, you should use STRICT with great care, if at all, because of the issues of typesafe linkage.