TN012: Using MFC with Windows 3.1 Robustness Features

Note   This Technical Note was written for Windows 3.1. Windows NT implements most of these features. When running your application under Windows 3.1 (with the Win32s DLLs), these techniques can aid in debugging. Windows 95 also implements and extends the robustness features put in place during Windows 3.1. Running the debug version of Windows 95 is the best way to insure your application is running cleanly.

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

These features include the debug kernel, STRICT type checking, diagnostics and memory management, and the WINDOWSX.H enhancements.

Windows 3.1 Debug Kernel

Note   This section applies only to the Microsoft Visual C++ version 1.5.

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. One machine, your test machine, should always run with the debug kernel. The other machine, your development machine, should run with the non-debug kernel. The output from the debug kernel can be sent to the primary machine over a null modem line. If you have only a single machine, then you should be sure to run the debug kernel (there is a slight performance degradation). The output from the debug kernel can be routed to DBWIN, a tool included with the Microsoft Visual C++ version 1.5. In addition, the Visual C++ Output window will receive output when running under the debugger.

A useful trick for single-machine debugging is to place copies of the system and debug binaries and symbols in a separate directory and to 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 non-debug. The Visual C++ version 1.5 installation program will set this up, then you can switch between debug and non-debug 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 Visual C++ version 1.5.

Below are some common programming errors that appear frequently in shipped Windows applications. Many of these problems can cause random system UAEs and other problems under Windows 3.0. The debug system binaries will help you track down problems, such as:

  • Passing invalid parameters of all shapes and sizes.

  • Accessing nonexistent window words. In Windows 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. (The suggested technique is to make use of Microsoft C/C++ feature of marking these functions as EXPORT in their definition.)

  • 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 runtime and compile time error trapping of your application. These functions are outlined below, but all are documented in the Class Library Reference manual.

Every class derived from CObject 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 the debugger or placed within #ifdef _DEBUG /#endif portions of your code. A helper function AfxDump is included in the debug library just for this purpose. It is called with a single parameter, a CObject*. You can call this function from the debugger to print out the argument. 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 standard MFC CDumpContext, afxDump, which by default goes to the debugger output window or to your debug terminal. You can also use the DBWIN program to view the output of afxDump. The source file MFC\SRC\DUMPINIT.CPP includes information on how to route afxDump to another destination.

TRACE, a macro that behaves much like printf, only 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 Technical Note 7 for more information on debug trace.

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 the comment, but it can evaluate the expression in the assertion macro. ASSERT statements have no effect in retails builds. If you need the information from ASSERT in the retail build, then use the VERIFY macro.

MFC also includes an extensive diagnostic memory allocator. You use the diagnostic memory allocator to check that you free all memory resources during certain program functions. 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 allocations that remain.

By default, MFC will dump all objects not freed by your program (if there are any) before your program exits. You can view this output by running your application under the debugger.

Windows 3.1 STRICT Type Checking

STRICT type checking is an option available with the WINDOWS.H header file. MFC uses these STRICT types by default, and you must use them if you are building an MFC application. MFC no longer supports building an application without the STRICT defintions.

Typesafe Linkage and STRICT

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, etc.

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 that is why all APIs in WINDOWS.H are in such a block.

STRICT type checking in WINDOWS.H enhances type safety for Windows programs by using distinct types to represent all the different HANDLES in Windows. So 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 changes the types of the various Windows 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 type checking 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 type checking 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.

If you are mixing STRICT and non-STRICT code, you must be aware of linkage inconsistencies. In general, all MFC programming and all C++ should be done with STRICT. If you have legacy C code, then not using STRICT is acceptable.

Windows 3.1 WINDOWSX.H Header File

New with Windows 3.1 and Win32 is the WINDOWSX.H header file that supports various extensions to the C-coding style for Windows programmers using C. These macro APIs, message crackers, and control APIs are defined in the file WINDOWSX.H.

This syntax is primarily designed for C programmers. MFC supports the use of WINDOWSX.H, so if you have existing code that relies on these tensions, use this code unmodified in MFC. You will find, however, that MFC has comparable idioms for all of the features of WINDOWSX.H, and uses the C++ language to accomplish these tasks with more semantic and architectural safety.

To use WINDOWSX.H, be sure to #include it before you have included AFXWIN.H (or STDAFX.H if you are using the AppWizard structure).

The only caveat is that there are two WINDOWSX.H APIs that collide with the MFC C++ APIs. The two APIs SubclassWindow and CopyRgn are not available for use within MFC. You will need to recode these to use either the MFC API (and classes) or to call the Windows API directly. You can also code your own macro as long as it has a different name.

Technical Notes by NumberTechnical Notes by Category