Migrating from C to C++ Using the Microsoft Foundation Class Library

This migration guide is for the software developer with a large body of Windows™ code written in C. Except for your existing code, moving to C++ and the Microsoft Foundation Class Library (MFC) looks attractive. You've heard about the benefits of C++, and many developers are touting all the professional features you get with MFC. Besides that, the bar keeps getting higher with OLE and new versions of Windows to support, not to mention multiplatform opportunities. But you have all that C code.

Read on.

This migration guide tells you how to convert a C program for Windows to C++, using MFC. You can gain most of the benefits of C++ and MFC with a minimum of pasting code into MFC classes and a minimum of C-to-C++ translation. The secret? An MFC function called CWnd::WindowProc, which was designed precisely for this purpose.

Note This migration guide supports the Microsoft Visual C++™ development system, Symantec® C++™, Watcom™ C/C++™, and MetaWare® High C/C++™, all of which supply MFC. Most of these products also supply tools to help you create the framework of your application initially and then "wire up" much of the additional functionality. Visual C++ supplies AppWizard and ClassWizard. Symantec C++ supplies AppExpress and ClassExpress. Watcom C/C++ can exploit the technology of MFC add-in tool vendors such as Visual Programmer™ from Blue Sky. MetaWare C/C++ can integrate with the Visual C++ shell and so can use Visual C++ tools.

Where it's necessary to mention one of these tools, this migration guide mentions the Visual C++ tool. Users of the Symantec, Watcom, and MetaWare tools should substitute the appropriate name and consult the related documentation.


MFC Benefits

Once your application is an MFC application, many possibilities are open to you:

  • Easy migration to newer versions of MFC and easier ports of 16-bit applications to Win32®.
  • Portability to other platforms, such as the Apple® Macintosh® or Windows NT™ on one of several RISC machines.
  • Readiness for Windows 95, (code-named "Chicago").
  • Database access via Open Database Connectivity (ODBC).
  • Much easier implementation of OLE servers, containers, visual editing, and Automation.

Those benefits are in addition to standard MFC support for many features that would otherwise be difficult —such as:

  • Toolbars, status bars, dialog bars, and property sheets ("tab dialogs").
  • A variety of user-interface models, including single document interface (SDI) and multiple document interface (MDI).
  • Splitter windows and other kinds of multiple-view configurations.
  • Printing and print preview.
  • Document/view architecture with support for file serialization.
  • Use of MFC in your DLLs as well as EXEs, and linking with the MFC libraries either statically or dynamically (MFC in a DLL).
  • The ability to call the Windows API from MFC code.
  • Support for Unicode (under Windows NT) and double-byte character sets (DBCS) for international markets.
  • And a great deal more.


The Migration Process

This section presents an overview of the migration process.

The general migration process occurs in three phases. You'll have a working application by the end of Phase 1; after that, you can choose whether you want to integrate your application more tightly with MFC in Phases 2 and 3. A detailed explanation of the process begins in Preparation.

The following summarizes the preparation steps and the three Phases.


Preparation

Phase 1 is preceded by some preparatory work:

1. Preparing your C code

This includes compiling your C code with a high warning level and checking it for migration issues with the MFC migration tool.

2. Creating an MFC Skeleton Application

This includes creating an MFC skeleton application, moving your C code into the MFC project, merging your resource file with the MFC project's resource file, and trimming your include lists.


Phase 1. Migrate Your WinMain and WndProc Code

In Phase 1, you'll follow these steps:

1. Migrate the WinMain Function.

MFC provides its own WinMain, so rescue any special code and delete your WinMain function.

2. Migrate WndProc into a C++ class.

Most of your primary WndProc function maps to the MFC view class. For Phase 1, you'll move the switch statement from WndProc to a view-class member function.

3. Move your WM_PAINT code into a member function.

MFC handles the WM_PAINT message in a special way, mapping it to the view class's OnDraw member function.

4. Move your WM_COMMAND code into a member function.

MFC also handles WM_COMMAND messages in a special way. Normally, the command message for each command ID maps to its own handler function, but for Phase 1, you'll map all WM_COMMAND messages to a single view-class member function.

At this point, you'll have a working MFC application. The purpose of this guide is to help you migrate your code to C++ and MFC as quickly as possible, so your application is not fully integrated with MFC yet. Phases 2 and 3, if you choose to undertake them, will increase the level of integration.


Phase 2. Get Rid of Your Message-Handling Switch Statements

In Phase 2, use ClassWizard to create separate message-mapped MFC handler functions for the remaining cases in your message handling switch statements. This lets you use the MFC message map, which causes the appropriate message handler to be called in response to each Windows-based message.

In addition to mapping messages, you can accomplish a couple of other useful things in Phase 2:

  • Use MFC's menu command updating mechanism to replace the way you enable/disable or check/uncheck menu items.
  • Replace your use of cbWndExtra and cbClsExtra with class member variables.


Phase 3. Integrate Further with MFC

Phase 3 includes a number of extra steps you can take to integrate your code more tightly with the MFC framework and take advantage of the framework's facilities:

  • Make graphics device interface (GDI) calls the MFC way.

    Now that you're working with C++ objects, there's a new syntax to follow.

  • Hook up MFC diagnostics.

    The MFC framework incorporates several diagnostic mechanisms, including dumps of your objects' contents and easy validation of an object's internal state.

  • Convert some of your C data structures to C++ classes.

    When you move from C to C++, you'll want to start taking advantage of the object-oriented features of the language to build encapsulated data structures with classes.

  • Move some of your data structures into the MFC document class.

    MFC introduces a document/view architecture: a document object stores your data, and a view object displays the data and interacts with the user. You don't have to use much of this architecture for Phase 1, but later there are advantages to working with it.

  • Use MFC's document serialization for input/output.

    If you use the document/view architecture, MFC supplies a protocol for storing and loading files. Serialization makes basic I/O very easy.

  • Move your printing code into the MFC view class.

    MFC view classes let you use the same code (in most cases) to draw either to the display or to a printer (or to a print preview display).

  • Use a different MFC view class for scrolling support, form-based viewing, database access, and so on.

Tip Use the MFC Migration tool to help work through the porting issues in your code. See Using the MFC Migration Tool.

Figure 1 shows the conversion process schematically. In the upper path, which includes steps 1 and 4, you compile your C code with a high warning level and then recompile it as C++, fixing errors until it compiles successfully. In the final step at the lower right, you migrate portions of your new C++ code into a new MFC skeleton application.

Beyond that, there are other things you can do, but just doing this much accomplishes several goals. It:

  • Turns your C code into C++ code.
  • Gets you into MFC.
  • Adds any MFC options you choose in AppWizard: for example, a toolbar, a status bar, printing and print preview support, MDI or SDI, a three-dimensional look to the controls in dialog boxes, custom window frame styles, splitter windows, and database and OLE support.

Tip Before you continue, it might be wise to learn the fundamentals of MFC by trying out the Scribble tutorial. In Visual C++ version 2.0, the tutorial is in Introducing Visual C++. In earlier versions, the tutorial is in the Class Library User's Guide.


The Migration Kit and the Example Program

Your C to MFC Migration Kit consists of the following:

  • This migration guide, both in print and as a help file on the distribution diskette.

    The guide describes migration from C to MFC and explains where porting from 16 bit to 32 bit fits into that migration strategy.

  • Several sample programs on the distribution diskette.
    • SHOWDIB, 32 bit, original and migrated
    • SHOWDIB, 16 bit, original and migrated

      These samples are located in the following directories on the diskette:

Sample | Location
16-bit before migration: | \SAMPLES\WIN16\SHOWDIB
16-bit after migration: | \SAMPLES\WIN16\SHOWD
32-bit before migration: | \SAMPLES\WIN32\SHOWDIB
32-bit after migration: | \SAMPLES\WIN32\SHOWD

  • The MFC Migration Tool on the diskette (16- and 32-bit versions).

    The Migration Kit includes a tool that scans your C source files and offers suggestions for things you should do to migrate the code to MFC. For more information, see Using the MFC Migration Tool.

  • Case studies for MFC, in print.

    Descriptions of how industry leaders have successfully used MFC to develop their products. In the leaders' own words, the case studies describe how MFC saved their development teams time and money, and what it really took to do the job.


Installing the MFC Migration Kit

Þ To install the MFC Migration Kit

1. Insert the distribution diskette into a disk drive.

2. From File Manager, run SETUP.EXE on the diskette.

Setup installs the kit in the location you specify.


Which First --Convert to MFC or Port to 32 Bit?

Which First —Convert to MFC or Port to 32 Bit?

In addition to converting your code to C++ and MFC, you may be planning to port a 16-bit version to 32 bit. If so, you're advised to convert to MFC first.

Porting a 16-bit C application to Win32 is harder and more time-consuming than porting a 16-bit MFC application to Win32. The MFC framework takes care of many of the details, such as message unpacking, that you would otherwise have to do yourself.

Tip For information on 16-to-32-bit MFC porting, see the article "MFC: Porting MFC Applications to 32 Bit" in Programming with the Microsoft Foundation Class Library in Visual C++ 2.0. For Visual C++ 32-Bit Edition, the earlier 32-bit release, see "Microsoft Foundation Class Library Update" in the Class Library Reference.


The SHOWDIB Sample Program

To illustrate the migration process with real code, this migration guide uses the SHOWDIB application. SHOWDIB is a Windows SDK sample application available with both Visual C++ 1.5 (16 bit) and Visual C++ 2.0 (32 bit). Both versions are also supplied on the disk accompanying this Migration Kit.

SHOWDIB lets you load, print, manipulate, and save bitmap files in the device independent bitmap (DIB) format. Unlike ordinary bitmaps, DIBs don't require that you furnish versions of the same bitmap for different screen resolutions. A bitmap stores information about each pixel in the image. DIBs store the bits of the image plus a color table, which makes them independent of any particular device. Figure 2 shows SHOWDIB's user interface.

Although SHOWDIB is not the most interesting application in the world (nor the most elegant), it has some advantages for the purposes of this migration guide. First, it's available in both 16-bit and 32-bit versions. Second, it's large enough to be interesting as a conversion target but small enough to be manageable in a relatively short guide. Finally, as the backbone of the guide, it's something you can use to walk through the process in a reasonable amount of time before you attempt converting one of your own applications.

The code examples presented in the guide are from the 32-bit version of SHOWDIB. Pertinent differences between the 16-bit and 32-bit versions will be pointed out as they come up.


Preparation

The preparation phase includes the following steps, which are described in full detail later in the guide):

1. Prepare your C code by compiling it with a high warning level to make it as clean as possible for the migration.

2. Install and use the MFC migration tool, MIGRATE.EXE, to check your C code for migration issues.

3. Create an MFC skeleton application using AppWizard.

The files created and the general way you use them are described in The MFC Files and Working with an Application Framework.

4. Copy your C source files into the MFC project and formally add your files to the project.

5. Merge your resource file with the MFC project's resource file to preserve your existing menu, dialog, icon, and other resources.

6. Trim your include lists; include the STDAFX.H file instead.


Preparing Your C Code

Before you begin converting your C code to C++, make sure your C code is as clean as possible. Then use the MFC migration tool to examine the migration issues for your program.

Using your current project and source files, be sure you can build and run your program with a very high warning level. Consider using the /W3 and /WX compiler flags. The /W3 flag specifies a high warning level. The /WX flag gives you errors, not just warnings, on any dubious code in your program.

Tip In Visual C++ 2.0, set the Warning Level and Warnings as Errors options on the C/C++ tab in the Project Settings dialog box. In Visual C++ 1.5 and earlier, set these options in the Custom Options category under C/C++ Compiler Options in the Project Options dialog box. See your C++ documentation.

Figure 3 shows the Visual C++ 2.0 options for setting the /W3 and /WX flags.

You might also want to run your program under the Windows debug kernel. For 16-bit work, you can install the debug kernel through Visual C++ 1.5 Setup.

For the SHOWDIB migration sample, open the existing SHOWDIB C-language project on the distribution diskette (or your Visual C++ installation). Set the compiler flags and compile. Fix any errors.

At this point, your code should be as clean as possible and ready for conversion to C++.


Using the MFC Migration Tool

Once your C code is as clean as possible, use the MFC migration tool to learn more about the migration issues that apply to your program.

The MFC migration tool, MIGRATE.EXE, examines your code, file by file, against a database of porting guidelines. For example, if you open SHOWDIB.C with MIGRATE.EXE, you can step through the migration issues in the .C file. The tool tells you, for instance, that "WinMain is not needed in an MFC Application" and tells you where to move pertinent WinMain code in the MFC framework.

The tool steps through your code, highlighting issues. You can use the tool to do simple editing of your source code, and it can make some simple changes, but only under your direction. The tool will identify potential problem areas in your C source code and give you advice towards cleaning it up. MIGRATE.EXE is a standalone tool that works in conjunction with your C++ development environment (for example, Visual C++). You should do major migration changes and significant source code editing, as well as compiling and debugging the resulting application, from within your C++ development environment.

The main issues you are likely to find when you use the migration tool are listed in Issues Shown by the Migration Tool. This guide is available as a Help file that you can access from the tool.

Note The MFC migration tool also comes with an additional database, C16_C32.PT, use when you migrate your code from 16 bit to 32 bit as well.

Þ To use the MFC migration tool on your C files

1. Run MIGRATE.EXE.

2. Use the Open command on the File menu to open the file for which you want migration information.

The tool is an MDI application, so you can have multiple files open at once.

3. Use the Set Migration Options command on the Migrate menu and select one or more of the categories listed in the dialog box. Choose OK when you finish.

Tip For migration to MFC, select each of the Phases listed in the Migration Options dialog box in turn. That is, work through Phase 1 before you examine the issues for Phases 2 and 3.

You can double-click an option in the dialog box to toggle whether it is marked or not.

To include an option, select it with the mouse or keyboard and choose the Mark button.

To remove an option, select it with the mouse or keyboard and choose the Unmark button.

4. Use the Migrate Current File command on the Migrate menu to examine migration issues in the opened file.

5. In the Migrating dialog box, note the first migration issue on display in the edit boxes.

The item that triggered the issue is highlighted in your source file window.

The upper box in the Migrating dialog box describes the potential problem; the lower box tells you in general terms what to do about it or suggests some replacement C++ code. If you agree with the suggested replacement C++ code, you can choose the Replace button to replace the highlighted C code with the replacement code and continue the search.

When the tool finds an issue, you can use the Help button to display the appropriate help topic in the online version of the MFC Migration Guide.

6. After viewing a migration issue, choose the Continue button to display the next issue.

7. Repeat steps 2 through 6 for each file in your project.

The MFC migration tool uses porting database files (files with a .PT extension). These are simple text files containing blocks of information used to scan your code and display issues during the migration process.

Tip The migration tool has editing capabilities, so you can make simple code changes directly as you are examining your files.


Migration Help

You can call up this migration guide as Help from the Help menu. The Online Migration Guide command opens the guide at the beginning. The Migration Process command opens the guide at the process summary in The Migration Process. While you're using the migration tool's Migrating dialog box for an MFC migration, you can use the Help button to display help for the current migration issue.


Issues Shown by the Migration Tool

To use the migration tool, run the tool against each C file from the C-language version of SHOWDIB. In the Migrating Options dialog box, select Phase 1. Later, you can select each of the other migration phases listed.

For SHOWDIB, run the MFC Migration Tool on the .C files in the SHOWDIB sample directory. Note the issues that the tool points out. This guide will take up most of those issues. The main isssues (corresponding to information in this guide) include:

In Phase 1:

  • WinMain is not needed in an MFC application.
  • WndProc functions need to move into overrides of the WindowProc member function in your MFC derived view class.
  • WM_COMMAND code belongs in an override of the OnCmdMsg member function in your MFC derived view class (and will later be moved into individual message-mapped handler functions).
  • WM_PAINT code belongs in an override of the OnDraw member function in your MFC derived view class.
  • Disambiguating calls to Windows API functions. The tool flags the names of Windows API functions that MFC encapsulates; you should prefix these with the C++ scope resolution operator (::).
  • Calls to DefWindowProc and Other Non-MFC Functions. You don't use DefWindowProc in MFC applications.
  • Window registration calls outside WinMain. These need to be checked.

In Phase 2:

  • Handler code for other Windows-based messages (such as WM_KEYDOWN) in your switch statement in your WindowProc function override should ultimately move into individual MFC message handler functions. The tool flags WM_ messages.
  • Handler code for WM_COMMAND messages in your switch statement in the OnCmdMsg function override should also move into individual MFC message handler functions.
  • Using MFC menu update handlers to update the state of your menus and toolbar buttons.
  • Replacing your use of cbWndExtra and cbClsExtra in the WNDCLASS structure, along with calls to the SetWindowWord, GetWindowWord, SetWindowLong, GetWindowLong, SetClassWord, GetClassWord, SetClassLong, and GetClassLong functions.

In Phase 3:

  • In the long run, you should adopt the MFC way of doing things. This includes transliterating calls to the Windows API functions, including global GDI function calls, to use C++ syntax and MFC, member functions. The tool flags GDI function calls. (The functions flagged for disambiguation in Phase 1 are also prime candidates for conversion to the corresponding MFC functions that encapsulate them.)
  • Moving your printing code to MFC. You should consider moving your old print loop code into the MFC view class. The tool flags calls to the StartDoc, StartPage, EndPage, EndDoc, and SetAbortProc functions.
  • Using document serialization for input/output. The tool flags calls to the OpenFile, _lread, _lwrite, _lopen, and _lclose functions.


Creating an MFC Skeleton Application

Before you move any of your code anywhere, use AppWizard to create a new MFC skeleton application. The files and classes that AppWizard produces are described in The MFC Files. Using those files and classes is described in Working with an Application Framework.

Tip Running AppWizard is described in the Visual C++ User's Guide for Visual C++ 2.0 and in the Visual Workbench User's Guide for earlier versions. See the documentation for your C++ development environment.

For SHOWDIB, create a new MFC project. Specify the name SHOWD for the project and its directory. In a later step you'll copy your C-language files into this project directory. As you work through AppWizard, select the following options:

  • Single document (for a single-document interface (SDI))
  • Printing and Print Preview

Also, deselect the following options, which you don't need for this example:

  • Dockable Toolbar (or Initial Toolbar in Visual C++ 1.5)
  • Initial Status Bar (Visual C++ 2.0)
  • Use 3D Controls (Visual C++ 2.0)

Otherwise, accept all of the default options. You won't need any of the database, OLE, or other options for SHOWDIB.

Tip For your first migration, don't go overboard with MFC options. Add only the features you're willing to support in the long run. Keeping the skeleton application simple will make the migration process simpler.

Later in the process, you can remove the parts of your code that MFC implements for you. For example, if your application is MDI, you can scrap your MDI code, because MFC does MDI for you. Keep in mind that it's often (but not always) difficult to add an AppWizard feature after the fact. Adding AppWizard features later is discussed in Chapter 15 of Introducing Visual C++ (Visual C++ 2.0) or Chapter 10 of the Class Library User's Guide (earlier versions).

AppWizard lets you name your classes and the files they are in. Table 1 lists the names used in this migration guide to describe the classes and files in the MFC version of SHOWDIB. If you practice migrating SHOWDIB yourself, it will help if you use the same names. (You can avoid a name clash with one of SHOWDIB's files by not using "showdib" as your project name.)

Table 1 File and Class Names Used for SHOWDIB in This Migration Guide

Class Name | Filenames | Comment
CShowDibApp | SHOWD.H/.CPP | Application class
CShowDibView | SHOWDVW.H/.CPP | View class
CShowDibDoc | SHOWDDOC.H/.CPP | Document class
CMainFrame | MAINFRM.H/.CPP | Frame window class
 | SHOWDIB.H/.C | Main original code file 1
1 Later you will move SHOWDIB's original .C files into the project, but nearly all of the code you'll migrate is in just two files: SHOWDIB.H and SHOWDIB.C.

In Visual C++ 2.0, AppWizard lets you change class and filenames at the end of the AppWizard process. In earlier versions, use the Classes button in AppWizard. In either case, select the class and fill in the edit boxes for class and filenames.


The MFC Files

When you finish, AppWizard creates a "project," including a set of files that contain the basic elements of an MFC application, implemented as C++ classes:

  • A document class.

    You won't use this class initially, but its ultimate purpose in MFC is to house the data for a document ¾ usually the unit of data that the user works with between File Open and File Close commands. Most documents are based on disk files, although they need not be.

  • A view class.

    This is the class you'll use most for this migration guide. A view is a child window embedded in the client area of a frame window. It manages the client area by displaying the data from an associated document. It also takes mouse and keyboard input, which it translates into selection and editing actions. In the simplest terms, the view class is where you put your client-area code. The default view class takes care of a lot of functionality, but you must supply the display code and the input-handling code.

Note For more information about documents and views, see Documents and Views.

  • A main frame window class (and, if your application is MDI, one or more child frame window classes).

    In an SDI application, a frame window object frames a view (or sometimes multiple views, as in the case of splitter windows) and routes messages to it. In an MDI application, the application's main frame window contains document frame windows (MDI children), one per open document.

  • An application class.

    The one and only application object in an MFC application manages application-level functionality. The application class implements common menu commands, including File Open, File New, and File Exit. It also supplies the InitInstance function that you use to initialize your application as a whole. The application creates and communicates with documents via "document template" objects.

What you get in the files created by AppWizard is an application-specific C++ class derived from each of the following MFC base classes: CDocument, CView, CFrameWnd (or CMDIFrameWnd and CMDIChildWnd), and CWinApp. By virtue of C++ inheritance, these classes contain all the behavior of their base classes plus whatever you choose to add to them. The rest of this migration guide concerns what you add and why and where you add it.

Figure 4 shows the objects in a running MFC SDI application and how they communicate.

The parts of this framework that you'll use in the procedure described in this migration guide are quite limited. You'll move most of your code into three functions in your MFC application's view class, derived from CView. That move takes you a long way. Eventually, you can move parts of your message handling code into other functions in other classes (see Phase 2).

Even before you add a line of your own code to the MFC skeleton that AppWizard creates, you can build the project and run the skeleton application. The application lets a user open windows, some of the menus already work, some of them bring up dialog boxes, and the application has any features you selected when you ran the wizard —such as a toolbar, a status bar, an About dialog box with a 3D look, and so on (for SHOWDIB, you omitted some options). You're looking at the tip of the MFC iceberg; MFC — an "application framework" — implements a great deal of standard Windows functionality that you don't have to program yourself.

Try out the skeleton by compiling and running it now. Then you can start filling in the skeleton.


Working with an Application Framework

Using an "application framework" like MFC rests on three key C++ concepts:

  • Class derivation
  • Inheritance
  • Extending and overriding inherited functionality

If an MFC class doesn't do quite what you want, you derive a new C++ class based on the MFC class. Your derived class inherits all of the member variables and member functions of its base class (and of that class's base class, and so on).

To add new behavior to your derived class, you extend it by adding new member variables and functions. If an inherited member function doesn't do what you want, you override it by defining a new function of the same name. Your overriding function can, if you wish, call the base class function that it overrides, to get the base class functionality in addition to your extended functionality.

MFC programming is a matter of extending and overriding MFC base class behavior. The programming model is:

1. Locate the seat of the MFC functionality that interests you. Source code is provided along with the documentation.

2. Derive a class and modify the base behavior through extension and overriding.

If you choose to practice migrating SHOWDIB, you'll work in three derived classes (AppWizard provides shells of these particular derivations) and four overridden member functions of those classes. AppWizard writes the shell of one of the overrides for you; you do the rest using ClassWizard.


Moving Your Code into the MFC Project

Before any real conversion begins, you need to get your source code set up as part of your new MFC project and do some limited cleanup work. Here are the general steps in brief (details follow):

1. Copy source files from your C project into the MFC project directory.

Caution See the following discussion in Copying C Files to the MFC Project for some files you should not copy over.

2. Rename the .C files to .CPP and formally add them to the project.

3. Merge your C project's .RC file with the one in the MFC project. The discussion below explains how to do this.

4. Trim unnecessary #include directives in your files, replacing them with:

#include <stdafx.h>

When you finish these steps, you've completed the preparation. Your files and resources are merged with the MFC skeleton application but not yet integrated with it. Building the project now will result in numerous errors. Phase 1 begins the migration in earnest and corrects those problems. By the end of Phase 1, you will have a running application that does what your C application did and also takes advantage of some of MFC's features.


Copying C Files to the MFC Project

Copy all .C and .H files (except RESOURCE.H) from your original project to the new MFC project. Also copy any other files specific to your C-language project — but don't copy your old project's .MAK file or its .RC file. The MFC project already has its own versions of the files you don't copy. Besides the .MAK and .RC files, you can skip copying such Visual C++ housekeeping files as the .APS, .VCP, .WSP, or .VCW files.

For SHOWDIB, you'll copy DIB.C, DLGOPEN.C, DRAWDIB.C, PRINT.C, and SHOWDIB.C. You'll also copy SHOWDIB.H and *.BMP. If you're doing the 16-bit SHOWDIB sample, also copy DLGOPENA.*.

Rename all .C files in the MFC project directory to .CPP. Then add the renamed files to the project — that is, besides copying the files into the project directory, you must formally let the project know that the new files now belong to it.

Tip To formally add files to your project in Visual C++ 2.0, use the Project Files command. In earlier versions, use the Project Edit command. See your C++ documentation.


Merging Resource Files

AppWizard creates a .RC file for the new MFC project, containing some valuable resources. But you don't want to abandon your old resources either, so you must consolidate the two files, using the new one as a base.

Copy individual resources from your old .RC file, resource by resource, into the new project's .RC file, as described below. First read the general procedure, then take a look at the specifics for SHOWDIB.

Þ To copy resources from one file to another in Visual C++

1. Open the new .RC file, then open the old .RC file in its original directory.

When you open an .RC file, Visual C++ (App Studio in versions before 2.0) compiles it and presents a window containing various categories of resources.

2. Position the two .RC file windows side by side in Visual C++.

3. Copy individual dialog, menu, icon, and other resources from the old file to the new.

Tip To copy a resource, hold down the CTRL key; then drag the resource from the old file and drop it onto the new file window. Without the CTRL key, you'll move the resource, not copy it.

Figure 5 shows two .RC windows open in Visual C++.

When you save the new file, the MFC project's RESOURCE.H file is updated with IDs for the added resources.

Note RESOURCE.H is a standard MFC file that contains ID definitions for commands, dialog boxes, and so on. AppWizard and ClassWizard do most of the work of updating this file for you. The documentation describes its use, including advanced options. The best way to examine the symbols defined in your MFC application in Visual C++ 2.0 is to use the Resource Symbols command. In earlier versions, use the Edit Symbols command in App Studio. See your C++ documentation.

For SHOWDIB, merging resource files involves copying one icon, one menu, and several dialogs:

  • To handle the icon resource, first delete the "IDR_MAINFRAME" icon resource under Icon in the MFC project. Then copy the "SHOWICON" resource under Icon in the C project into the MFC project and rename it to IDR_MAINFRAME. You want the original SHOWDIB icon to replace the default application icon that AppWizard created.
  • Handle the menu resource similarly. Delete the default menu resource created by AppWizard ("IDR_MAINFRAME" under Menu) in the MFC project, then copy over the one from the C project ("SHOWDIB" under Menu) and rename it to IDR_MAINFRAME.
  • Copy all of the dialog resources (under Dialog in the C project).

    Except for the About dialog box, you'll use all of these as they are. You can either use the original About dialog box from the C project (ID "101") or use the equivalent resource in the new project (ID "IDD_ABOUTBOX"). (If you use the About dialog box supplied by AppWizard, it displays the icon you copied over earlier to IDR_MAINFRAME.) You can edit the AppWizard dialog box as needed.

In your application, handle other kinds of resources similarly. In some cases, you want your preexisting resource to replace the default version created by the wizard. Where there is no duplication, you simply want to copy your resources into the new project.


Trimming Your Include Lists

Trim the list of #include directives at the top of your migrated files and replace them with an #include directive for STDAFX.H. Guidelines:

  • stdafx.h causes all of the header files needed by MFC to be included. These include AFXWIN.H (MFC Windows elements), AFX.H (non-Windows elements of MFC), AFXRES.H (MFC resource management), AFXCOLL.H (MFC collection classes), and others.
  • Among the files included by stdafx.h is windows.h. MFC applications must not themselves include WINDOWS.H.

For SHOWDIB, trimming the lists involves replacing all #includes of windows.h with:

#include <stdafx.h>

For other applications, you may need to more closely examine what STDAFX.H includes to see if it affects other includes in your application.


Phase 1: Migrating WinMain and WndProc Code

Phase 1 takes you through a number of necessary steps, ending with a migrated version of your application that runs successfully. You can actually stop at the end of Phase 1, but at that point your application doesn't yet take full advantage of MFC.

Phase 1 steps include:

1. Migrating the WinMain Function.

2. Migrating WndProc into a C++ class.

3. Moving your WM_PAINT code into a member function.

4. Moving your WM_COMMAND code into a member function.


Migrating the WinMain Function

MFC supplies its own WinMain function, so you don't need to supply one (it's possible to supply one and cause it to override MFC's WinMain if you really need to). This function, which you'll seldom need to worry about, registers a number of standard window classes and calls functions to initialize the application, run its message loop, and clean up after the application. MFC WinMain does most of what you'll normally need, and it allows you to customize its actions by inserting your own code into its initialization, idle processing, and cleanup routines.

Migrating WinMain involves rescuing any special code from the body of WinMain and fixing differences between C and C++ in your files.


Removing the Body of Your Old WinMain Function

Move any special initialization or idle-processing code from your C project's WinMain function (leave behind window class registration, window creation and display, and the message loop). Put the moved code into appropriate overridden CWinApp member functions in the MFC project, such as InitInstance, InitApplication, Run, or ExitApplication. You can put idle processing code into an override of the OnIdle member function. Guidelines:

  • Application-level initialization code probably goes into InitInstance. AppWizard provides an override of this function, so you can simply copy the affected code into InitInstance in the projname.CPP file. (InitApplication is used for initialization code that applies only to the first running instance of the application.)

Note PROJNAME is whatever name you specified for your project in AppWizard.

  • Application-level cleanup code goes into ExitApplication. AppWizard doesn't automatically override this function, so you must override it yourself.
  • Code that fits elsewhere in the execution sequence might go somewhere in the Run function, which houses the application's message loop.

Once you've removed the code you need to save, delete your entire old WinMain function. You'll rely on MFC's version instead.

Tip C++ allows you to "override" member functions of class A in your derived class B. To override function A::Fun1, derive class B from class A and declare function B::Fun1. For an override, A::Fun1 (or the Fun1 version in one of A's base classes) must be a virtual function; otherwise, you simply hide A's version with B's version. For more details, see To override a class member function

For SHOWDIB, move the following lines of code from the old WinMain function in SHOWDIB.CPP into the InitInstance function in class CShowDibApp, derived from CWinApp (in SHOWD.CPP):

hInst = hInstance ;

 /* Initialize clip rectangle */

 SetRectEmpty(&rcClip);

 if (!GetProfileString("extensions", "bmp", "", ach, sizeof(ach)))
         WriteProfileString("extensions", "bmp",
                                            "showdib.exe ^.bmp");
 if (!GetProfileString("extensions", "dib", "", ach, sizeof(ach)))
         WriteProfileString("extensions", "dib",
                                            "showdib.exe ^.dib");

 /* Save the pointer to the command line */
 lstrcpy(achFileName, lpCmdLine);

If you're migrating 16-bit SHOWDIB, also move over the following lines:

/* default to MEMORY DIB's if XWindows */
dwWinFlags = GetWinFlags();
bMemoryDIB = (BOOL)(dwWinFlags & WF_PMODE);

You don't have to override InitInstance yourself, because AppWizard does it for you. You'll need to make some changes to support this code in its new location, as described below.

Þ To make the code changes work in SHOWDIB

1. Rename the lpCmdLine variable in the lstrcpy statement to m_lpCmdLine.

For 16-bit SHOWDIB, rename lpszCmdLine to m_lpCmdLine.

This uses a member variable of the MFC application class, CShowDibApp, instead of the old variable. The old variable in SHOWDIB.CPP will be deleted when you delete the old WinMain.

2. Similarly, rename the hInstance variable just before the code that initializes the clip region to m_hInstance, another MFC application class member variable.

3. "Disambiguate" both sets of calls to GetProfileString and WriteProfileString by prefixing a double colon (::) to them:

if (!::GetProfileString("extensions", "bmp", "", ach, sizeof(ach)))
::WriteProfileString("extensions", "bmp", "showdib.exe ^.bmp");

The double colon is the C++ scope resolution operator, which here indicates that the functions are at file scope rather than class scope. In other words, MFC encapsulates or "wraps" these Windows API functions, but the calls here are to the Windows functions, not the wrapped MFC versions. This prevents an error in the number of parameters passed. For more information about disambiguating non-MFC function calls, see Disambiguating Calls to Windows API Functions.

4. Add the following #include to the SHOWD.CPP file:

#include "showdib.h"

5. Move the declaration of the ach local variable from SHOWDIB.CPP to SHOWD.H. In class CShowDibApp (SHOWD.H), add the ach variable to a new "// Attributes" section of the class declaration, as shown below:

class CShowDibApp : public CWinApp
{
public:
    CShowDibApp();

// Attributes
    CHAR        ach[40];    // for 16-bit SHOWDIB, it's char

// Overrides
    // ...
};

This isn't strictly necessary, but it illustrates a common practice in MFC. Variables that you might ordinarily define as globals are often turned into class member variables. This makes them "global" (at class scope) from the point of view of class member functions but keeps down the clutter of true globals.

6. Delete your old WinMain function completely.

Tip You might want to use MFC naming conventions for consistency. Class names begin with "C". Member variable names begin with "m_" ¾ so the ach variable above should really be m_ach (everywhere). MFC programmers typically also use Hungarian notation. Charles Petzold discusses Hungarian notation in Programming Windows 3.1 (Microsoft Press).


Fixing Differences Between C and C++

At this point, your C source files are in the project but not yet integrated into the MFC sources. Before taking further integration steps, you need to make your old C code compile as C++ code. When you build the MFC project, you'll encounter errors that stem either from the differences between C and C++ or from header incompatibilities. Build the project at this point. Then fix the errors.

For SHOWDIB, the great majority of the errors at this stage are type conversion problems. The main categories of errors encountered include:

  • Type conversion problems
  • Use of obsolete functions
  • Scope problems
  • Other errors.

Note The 16-bit version of SHOWDIB happens to use old-style function headers. So you should change all of the function headers in the original SHOWDIB .C files to use ANSI-style headers. This shortens the error list a great deal.

Scope Problems

As you work through the migration, you'll find a number of variables in the moved code that lose their moorings. You get linker or compiler errors because the variables are undefined in SHOWD.CPP. Sooner or later, you'll need the following declarations in SHOWDIB.H:

extern HINSTANCE      hInst ;
extern BOOL           bNoUgly;
extern WORD           UpdateCount;
extern BOOL           bUpdateColors;
extern UINT           nAnimating;
extern PLOGPALETTE    pLogPal;
extern CHAR           szBitmapExt[];   // for 16 bit, use char
extern HPALETTE       hpalSave;
extern BOOL           bMemoryDIB;      // add for 16-bit SHOWDIB

When you add these entries for 16-bit SHOWDIB, also make the following changes in SHOWDIB.CPP to match the data types listed above:

  • Find the definition of hInst, and change its type from HANDLE to HINSTANCE.
  • Find the definition of nAnimating, and change its type from WORD to UINT.

C++ is stricter than C about variable declarations and definitions. A variable definition causes storage to be allocated. A variable declaration, using extern, does not. It merely promises that a real definition exists somewhere in the program. The linker will manage the connection.

In SHOWDIB, these problem variables must be visible in various modules of the program — hence the extern declarations in SHOWDIB.H, which affected modules can include as needed. The variables must also be defined in some module, as most now are in SHOWDIB.CPP.

Type Conversion Problems

Numerous errors in both versions of SHOWDIB result, for example, from lines like the following (error code C2446):

lpbi = (VOID FAR *)GlobalLock(hbiCurrent);

where the VOID FAR* cast requires a more precise data type in C++:

lpbi = (LPBITMAPINFOHEADER)GlobalLock(hbiCurrent);

Similar type conversion problems occur in passing parameters, accounting for another large group of errors. In most cases, an improved type cast takes care of the problem, although actually changing variable or parameter types might be even better.

Use of Obsolete Functions

This accounts for only a few errors. For example, both versions of SHOWDIB use the MakeProcInstance function, which is obsolete in Win32:

lpfnPrintDlgProc = (DLGPROC) MakeProcInstance (PrintDlgProc, hInst); lpfnAbortProc = (WNDPROC) MakeProcInstance (AbortProc, hInst);

It's no longer necessary in Win32 to call MakeProcInstance (and often not necessary in 16-bit Windows, for that matter). The flat, unsegmented address space of Win32 makes it unnecessary to perform the data-segment switching "trick" the function used to perform. MakeProcInstance is a macro in Win32 that the preprocessor treats as just what you give it.

You can handle the occurrence of MakeProcInstance in SHOWDIB in two ways. The quick (but not pretty) way, which works for both versions, is to change some casts in the existing calls:

lpfnPrintDlgProc = (FARPROC) MakeProcInstance ((FARPROC)PrintDlgProc,
                                                hInst);
lpfnAbortProc    = (FARPROC) MakeProcInstance ((FARPROC)AbortProc,
                                                hInst);

In the 16-bit version of SHOWDIB, cast the second parameter in each call to HINSTANCE.

Inelegant Errors

Occasionally, code is somewhat less than elegant, as the translation process can reveal. In one place, SHOWDIB uses the cast to int in line 3 below:

if (bDIBToDevice) { (int)hMemDC = 1; // won't compile under C++ break; } ... if (!(hMemDC = CreateCompatibleDC (hDC))) ... if (!hMemDC) ...

In the 16-bit version, the cast to int is missing, so the error message is different, but the correction described below is still the answer. The 16-bit version requires more precise data types: assigning an integer to an HDC doesn't work in C++. For the 32-bit version, C++ doesn't allow the cast on the left side of an assignment; the language doesn't interpret the expression:

(int)hMemDC

(where hMemDC is of type HDC) as an l-value. A quick fix is the following:

hMemDC = (HDC)1; // will compile, but not pretty

The original code (and the quick fix) is dubious at best. The hMemDC variable, an HDC, is used as a Boolean condition in the final if statement ¾ if the DC has been successfully allocated, or if the flag bDIBToDevice is true (and hMemDC gets set to 1), hMemDC is nonzero, and this value is used in the final if statement. It would be better to revise the code to use another Boolean flag and avoid using an HDC this way.

Additional Errors in the 16-Bit Version of SHOWDIB

Most of the errors discussed in previous categories apply to the 32-bit version of SHOWDIB or to both versions. The 16-bit version produces a few other problems.

The IDM_ABOUT case for the WM_COMMAND message uses a dialog template resource ID no longer defined in the C++ project. To correct this, change the first parameter in the fDialog function call below from "ABOUT" to "IDD_ABOUTBOX":

case IDM_ABOUT:
        /* Show About .. box */
        fDialog (IDD_ABOUTBOX, hWnd,AppAbout);
        break;

A better improvement would be to remove that code and use the MFC About dialog box mechanism, but this simple fix will do. Also cast the last parameter to FARPROC.

Due to a bug in the 16-bit version, you need to remove the IDM_ANIMATE20 case in the WM_COMMAND code. There is no such case, and the identifier is undefined in the C++ version.

The following error in the 16-bit version:

c:\showd6\print.cpp(37) : error C2128: 'AbortProc' : alloc_text/same_seg applicable only to functions with C linkage

occurs because the following compiler-specific code is not supported under C++:

#pragma alloc_text(_PRINT, AbortProc, PrintDlgProc)

You could specify C linkage for the two functions using extern "C":

extern "C" AbortProc extern "C" PrintDlgProc

But the more portable solution, in case you ever plan to migrate the application to 32 bit, is simply to delete the line.

The 16-bit version of SHOWDIB uses an assembler module for a special version of the chdir function. Bearing in mind that you might also want to port the 16-bit application to 32 bit at some point, now is the time to adopt a portable solution. In this case, the function written in assembler has a counterpart in the C run-time library: the _chdir function. To use the portable version:

1. In the file DLGOPEN.CPP, remove the following line:

extern PASCAL chdir(LPSTR); /* in dlgopena.asm */

2. In the same file, replace the following line:

if (chdir ((LPSTR)szFileName))

with:

if (_chdir (szFileName))

If your own programs use assembler modules, you should seriously consider rewriting them in C++ to ease future portability to 32 bit.

Once you have fixed all errors, you can begin integrating your code into the MFC skeleton. If you run the application now, you see the SHOWDIB user interface, but all menus are disabled. You'll fix that shortly.


Migrating WndProc

Now the real conversion work begins. This consists largely of moving message-handling code from your WndProc function(s) into the WindowProc function of the MFC project's view class. (You'll learn more about the view class in Moving Your WM_PAINT Code into the View.)

The result, after you've fixed a few problems, is a very loose integration. The application works but doesn't yet take advantage of the MFC document/view architecture. Later, you can begin to use the document/view architecture by moving some code into the document and view classes. The strategy is to get your existing code running within the MFC framework with as little effort as possible. Once you've accomplished that, you can go on to integrate the code more tightly, gaining new access to MFC functionality and taking advantage of MFC simplifications as you go.

Here are the general steps you'll take (details follow):

1. Override the CWnd::WindowProc member function in the view class that AppWizard created for you.

You'll override the virtual function and cut out the body of your main WndProc function (in the .CPP version of the file in the MFC project). Paste it into WindowProc.

When you finish, you'll delete the rest of your old WndProc function.

2. Move your WM_PAINT code into the view class by overriding the CView::OnDraw member function in your derived view class.

AppWizard supplies the shell of this override. You'll cut the code that handles the WM_PAINT message and paste it into OnDraw.

3. Move your WM_COMMAND code into the view by overriding the CView::OnCmdMsg member function in your derived view class.

You'll override the virtual function and cut the code that handles the WM_COMMAND message. Paste it into OnCmdMsg.

4. You'll do the following in all of these overrides:

  • Make sure functions and global variables that used to be visible in your C code are still visible in the overrides.
  • Disambiguate calls to Windows API functions, using the C++ scope resolution operator (::).
  • In function calls that take an HWND as their first parameter, you'll replace the actual parameter with an HWND owned by the view class, using the CWnd::GetSafeHwnd function.

5. You'll bypass the MFC mechanism that updates the state of menu items — whether they're enabled/disabled or checked/unchecked.

Note In some cases, you may need to arrange special handling for certain messages. SHOWDIB illustrates this.

For SHOWDIB, work through the next sections to carry out these steps.


Overriding CWnd::WindowProc

The MFC skeleton that AppWizard creates contains several derived classes in which you can place your overrides of base class functions, including the view class, derived from CView.

CView is derived in turn from class CWnd, the base class of all MFC windows. CView inherits the WindowProc member function from CWnd. So does your derived view class— until you override the function, supplying your own version. For your view class, MFC calls the overriding version instead of the default version used for other windows.

If you had to override one of the member functions of class CWinApp earlier in this process, you know how to override a class member function. If not, follow these directions, which illustrate the general case with specific code for the WindowProc override you need for your C++ conversion:

Þ To override a class member function

1. Derive your own class, declaring the appropriate MFC class as its base class.

The syntax looks like this:

class CMyClass : public CBaseClass
{
    // class member variables and member functions
};

You can use ClassWizard to derive classes from MFC base classes and to perform parts of the next two steps.

2. In the .H file for your class, add a prototype for the overriding function.

For example, for any application you're converting to C++ with this migration guide, add a prototype for the WindowProc function in the view class (which AppWizard derives for you). Because it's a virtual function, a good place to put it is in an "// Overrides" section (with a "protected" specifier) as shown here:

class CShowDibView : public CView
{
    // ...
// Overrides
    // ...
    protected:
    virtual LRESULT WindowProc(UINT message, WPARAM wParam,
                                LPARAM lParam);
    // ...
// Implementation
// ...
    DECLARE_MESSAGE_MAP()
};

Notice that WindowProc is a virtual function. The virtual keyword is redundant in an override, but it serves as a reminder of this function's nature. The protected keyword means that WindowProc can only be called from within objects of class CShowDibView or classes derived from it. You can't call it from outside the class.

Tip In Visual C++ 2.0, ClassWizard will override virtual functions for you; all you have to do is fill in the function body in the .CPP file. In earlier versions, you must write the function prototype and body yourself. For syntax, see class CWnd in the Class Library Reference, or see your C++ documentation.

3. In the .CPP file for your class, add a function definition and fill in the details of what the overriding version does.

For example, the following code shows a WindowProc override for SHOWDIB's view class, added to the SHOWDVW.CPP file:

LRESULT CShowDibView::WindowProc(UINT message, WPARAM wParam,
                               LPARAM lParam)
{
    // your message handling code will go here

    return CView::WindowProc(message, wParam, lParam);
}

Notice that the override calls the WindowProc function in the base class, which this version overrides. In other words, CShowDibView's version of WindowProc does what it needs to do; then it calls the base class version to let the base class process any messages it wants to process.

For SHOWDIB, override WindowProc in the view class, as described above. AppWizard has already done step 1 for you; class CShowDibView is the derived view class. Complete steps 2 and 3 using ClassWizard. (For the 16-bit version, you'll have to do this override by hand; if you're using Visual C++ 2.0, ClassWizard can override virtual functions, but not in earlier versions.)

Tip Calling the base class version from an override is common in MFC. Whether you call the base class version before your own code, after it, or in the middle depends on your needs and on what the base class version does. Sometimes you want to override it completely; sometimes, as here, you just want to augment what the base class does. When you use ClassWizard to do the override, it suggests the most usual case by writing the base-class call for you. You can usually change its position if you need to.


Moving Your Code into WindowProc

This is the heart of your conversion to MFC. MFC calls the view's WindowProc before it passes incoming messages to its own "message map" mechanism. This means you can simply copy the typically huge switch statement from your C-language WndProc function into WindowProc. With a little tidying up, as described later, the program should then execute your message handlers as written.

For SHOWDIB, move your message handling code into WindowProc as follows:

1. Copy the entire body of your WndProc function, including local variable definitions, and paste it into CShowDibView::WindowProc.

2. Add the following include directive to file SHOWDVW.CPP:

#include "showdib.h"

3. Delete the entire WndProc function in SHOWDIB.CPP.

At the bottom of the function, AppWizard has written a call to CView::WindowProc, the base-class version of the function. This allows other objects in the MFC framework to handle messages that you don't handle. Leave that call in place, below all of your added code. The code from the default case on down should look like the following:

LRESULT CShowDibView::WindowProc(UINT message, WPARAM wParam,
                                 LPARAM lParam)
{
// ...
            break;

        default:
                ;
        }

    return CView::WindowProc(message, wParam, lParam);
}

Important In the MFC version, you don't call DefWindowProc to handle messages not handled by your code. Instead, the call to CView::WindowProc lets MFC take care of unhandled messages in its own way. For more information, see Calls to DefWindowProc and Other Non-MFC Functions.

Note If your C code includes WndProc functions for more than one registered window class, you need to work out how the code in those functions fits into MFC. The functions might be for additional document/view types in an MDI application, or for child windows that will now be children of your view. The only ones you really need to convert are those that map to MFC view classes.


Cleaning Up WindowProc

Next, you need to clean up WindowProc a bit. Check the variables used in the rest of WindowProc to be sure their declarations are visible to the function. Likewise, check functions called from WindowProc to make sure they're visible.

For SHOWDIB, you need to make several variables visible from within WindowProc, as discussed in Scope Problems. These variables are defined in SHOWDIB.CPP, so you need extern declarations for them in SHOWDIB.H. Then you can add an include statement for SHOWDIB.H to the SHOWDVW.CPP file.

The code you've just moved into the view class calls two macros, StartWait and EndWait (defined in SHOWDIB.H). To make those macros work, you need to add the following declaration in SHOWDVW.CPP:

static HCURSOR hcurSave;

This ensures the macros, which use the name hcurSave, will find it already defined within the current file, as it already is in SHOWDIB.CPP and DIB.CPP, where the macros are also used.


Disambiguating Calls to Windows API Functions

At its core, MFC wraps the Windows API (or large portions of it). MFC uses C++ classes to represent common Windows-based abstractions, such as windows, dialog boxes, device contexts, controls, brushes and other GDI objects, and so on. Most of these classes encapsulate a Windows handle, such as an HWND or HDC, as well as the relevant APIs that use that handle type. For example, class CWnd and all of the classes derived from it wrap an HWND by defining a class member variable to hold the HWND and class member functions to create and destroy the HWND and to otherwise operate on it. Most of the Windows API functions that take an HWND parameter are wrapped by CWnd or one of its derived classes.

Within an MFC program, though, you can freely call either the MFC wrapper function or the corresponding Windows API. You can get at all of the Windows API anytime you need to.

It can be pretty confusing if you're mixing MFC calls with Windows API calls, so you need to disambiguate your Windows API calls — the ones with the same names as MFC wrappers — using the C++ scope resolution operator (::). To use the scope operator, prefix the API name with it:

::BeginPaint(m_hWnd, &ps);

This lets the compiler know you're calling outside the class scope.

Tip If you aren't sure whether to disambiguate a function call, use F1 help in Visual C++ on the API name. Functions that MFC wraps bring up a dialog box asking you to specify whether you want help on the MFC version or the Windows API version. If the dialog box appears and lists "Class Library Reference," disambiguate the call. In most cases, the MFC version has a different parameter signature, and often a different return type. The MFC migration tool also helps you spot functions that must be disambiguated.

For SHOWDIB, whose message handling switch statement uses nothing but Windows API calls, you need to disambiguate lots of function calls. Adding the scope operator to nearly all calls that take an initial HWND or HDC parameter in your view's WindowProc, OnDraw, and OnCmdMsg takes care of most of them. The compiler will give you error messages about parameter types or numbers of parameters for the rest.


Replacing HWND and HDC Parameters

WindowProc is within the scope of the view class. That is, you can think of the function as being inside a CView-derived C++ window object. Also, WindowProc doesn't pass you an HWND the way your old WndProc did. This means that when you call Windows API functions directly, you should pass them the HWND belonging to the C++ window object, which is stored in the object's m_hWnd member variable. To do so, add a line of code like the following at the top of WindowProc:

HWND hWnd = GetSafeHwnd();

where hWnd is whatever name your code uses for the HWND parameter to Windows API functions. In effect, this globally replaces the old parameter throughout WindowProc with the correct value. GetSafeHwnd is a CWnd member function that returns a copy of the HWND stored in m_hWnd that is safe to use directly this way.

Note Calling GetSafeHwnd is a useful technique in the present context, but it's really not a common occurrence in most MFC code. It's used here to avoid manually replacing a lot of variable names.

If you're working with a member function that calls Windows API functions with an HDC parameter, you can do something similar. Add a line of code like the following at the top of your override:

HDC hDC = pDC->GetSafeHdc();

This code assumes you already have a pointer (pDC) to an MFC object of class CDC, which encapsulates a device context handle (HDC). If you don't have such a pointer, you can create a CDC-derived object as follows :

CClientDC dc(this); // a device context for the view (client area) HDC hDC = dc.GetSafeHdc();

The CClientDC constructor calls GetDC and the destructor calls ReleaseDC for you.

For SHOWDIB, add the HWND code shown above at the top of CShowDibView::WindowProc. You'll add the HDC code to another function override later.

Also, where SHOWDIB's WndProc has a parameter called iMessage, WindowProc's parameter is called message. To correct this difference, add the following line just before the switch statement:

UINT iMessage = message;

This is similar to the hWnd replacement code shown earlier.

Tip After cleaning up WindowProc (and after the next several steps in the process), you can compile the program. Parts of it won't work yet, but you can catch typographical errors.


Moving Your WM_PAINT Code Into the View

Now you can begin, in small ways at first, to take advantage of the document/view architecture in MFC. The basic idea is that storing and manipulating data is separated from viewing it. A document object stores your data structures and is associated with a view object that knows how to display the data and manage user interaction with it, such as selecting and editing the data.

Figure 6 shows the document/view relationship conceptually. Drawing in MFC is managed by the view's OnDraw member function. Migrating to this model involves moving your WM_PAINT code into that function.

Documents and Views

The document and view are closely associated objects . The view has a way to access the document's contents, which it must do to display the document's data and to inform the document of editing changes. The document also has a way of informing the view when the display needs to be updated. This might seem redundant, except that documents sometimes support multiple views of the same data. For example, the New Window command on the window menu in many applications supports opening a second frame window on the same document. Another example is splitter windows, like those in many applications, including Microsoft Excel™. When view A informs the document of an edit, you can call the document's UpdateAllViews member function to alert all related views that they must update themselves to obtain the changes. Multiple views usually show the same data in different ways. For example, one view of a hypothetical document might show its data in tabular form, while another shows the data as a graph.

Structurally, a view is a child window embedded in the client area of a frame window. In an SDI application, picture a child window that covers the entire client area of the application frame window. In an MDI application, views of documents are embedded in MDI child window frames. (The MDI child frames, in turn, are embedded in a special child window itself embedded in the application's main frame window. This child is called MDICLIENT; it manages the currently open MDI children.) When splitter windows are used, the client area real estate is divided among multiple view windows. The document keeps a list of pointers to all of the views of its data.

MFC's CView class is the base class for all view classes, including CScrollView, a view that manages scrolling for you; CFormView, a view based on a dialog template resource; CRecordView, a form view associated with a CRecordset object for database access; and other view classes. CView wraps the view window's HWND and supplies member functions for drawing, printing, and other actions. (Printing is discussed later, in "Moving Your Printing Code to MFC." Other view classes are discussed later in "Use a Different View Class.")

The OnDraw Member Function

The most interesting CView member function is OnDraw. CView also has an OnPaint function, which the framework calls in response to the WM_PAINT message. But instead of putting your painting code in OnPaint, you put it in OnDraw. OnPaint creates and prepares an appropriate device context (an object of class CDC), then calls your OnDraw override, passing it a pointer to the prepared CDC object.

Figure 7 describes the drawing process.

The only part of the process you're responsible for is writing the drawing code in OnDraw.

Here is a simple OnDraw function that draws some text at a given location within the view:

void CMyView::OnDraw(CDC* pDC)
{
    CMyDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);

    int x, y;
    x = y = 0;
    pDC->TextOut(x, y, "Hello, world!");
}

The key features of this code are:

  • The TextOut call, using the pDC pointer passed to OnDraw from OnPaint.
  • The GetDocument call.
  • The ASSERT_VALID macro.

What's different from C about this TextOut call? The syntax. First, there is no HDC-type parameter. Second, instead, TextOut here is a member function of MFC class CDC, not the familiar GDI call. You're passed a pointer to a CDC object, which wraps the HDC. You call MFC's version of TextOut through that pointer. CDC::TextOut does call the familiar Windows API function underneath, but the call you make is somewhat simplified. You'll see more of this in Make GDI Calls the MFC Way.

Tip The syntax shown is for accessing class members. In C++, classes and structures are closely related, so the syntax is like that for accessing struct members in C. A class and a struct are the same thing, with different keywords and default accessibility constraints. Both can have member variables and member functions, and sections of either can be declared public, protected, or private.

The GetDocument call returns a pointer to a CDocument-derived class. The example doesn't use that pointer for anything, but it could use it to access the data stored in the view's associated document and to call document member functions.

The ASSERT_VALID macro is part of MFC's diagnostic support, used to validate the document's internal state before attempting to do anything with the pDoc pointer. You'll learn about this macro later, in "Hook Up MFC Diagnostics."

Moving Your Paint Code to OnDraw

In SHOWDIB, when you move your painting code to OnDraw, you have to make some changes to it, as shown in the following code from SHOWDIB:

void CShowDibView::OnDraw(CDC* pDC)
{
    HWND hWnd = GetSafeHwnd();     // add these lines
    HDC hDC = pDC->GetSafeHdc();

    /* If we have updated more than once, the rest of our
     * window is not in some level of degradation worse than
     * our redraw...  we need to redraw the whole area
     */
    if (UpdateCount > 1) {
        ::ValidateRect(hWnd, NULL);   // replace Begin/EndPaint
        UpdateCount = 0;
        ::InvalidateRect(hWnd, (LPRECT) (NULL), 1);
        return;   // change break to return
    }
// remove BeginPaint
    AppPaint(hWnd,
             hDC,
             ::GetScrollPos(hWnd,SB_HORZ),
             ::GetScrollPos(hWnd,SB_VERT) );
// remove EndPaint
// delete break statement here

}

After moving the painting code to OnDraw, delete the WM_PAINT case from WindowProc. You can also remove the definition:

PAINTSTRUCT ps;

from WindowProc. If not, you're warned about an unreferenced variable.

Aside from the few changes described below, this version of OnDraw makes minimal changes.

Proper MFC usage, as you'll see later in Make GDI Calls the MFC Way, would differ quite a bit from what you see here. This code ignores the pointer to a CDC class object passed as a parameter, except to obtain a safe HDC to pass to the AppPaint function. The CDC object encapsulates a fully prepared device context.

The most essential change to the original C code is to remove the calls to BeginPaint and EndPaint. BeginPaint has been called before OnDraw is called; EndPaint will be called when OnDraw completes. To preserve the original logic, it's necessary to replace the first BeginPaint/EndPaint pair with a call to ValidateRect.

Moving the code from a switch statement into a function requires deleting the two break statements, replacing the first one with a return statement. This preserves the original logic (although it wasn't very elegant).

Notice the calls to GetSafeHwnd and GetSafeHdc at the beginning of OnDraw. The Windows API function calls have been disambiguated with the scope resolution operator. That too will change in Make GDI Calls the MFC Way.


Moving WM_COMMAND Code into the View

WM_COMMAND messages receive special handling in MFC. Instead of being sent only to windows, they are routed in a predetermined way among the objects¾ called "command targets" ¾ in the running application. Besides windows and views, other objects get a chance to handle commands, including documents. The idea is that a command ought to be handled by the object with the most relevant information about the command.

Each class derived ultimately from class CCmdTarget (including CWnd, CView, CDocument, and others) has an OnCmdMsg function. OnCmdMsg checks the object's message map and may also route the command to other command-target objects. (Message maps are discussed later in "Using the MFC Message Map.")

Figure 8 illustrates MFC command routing in a simplified way.

For this conversion process, you simply move all WM_COMMAND code into an override of CView::OnCmdMsg.

For SHOWDIB, override OnCmdMsg in the view class and move all code now in the MenuCommand function (SHOWDIB.CPP) into OnCmdMsg. A much-abbreviated version of the view's OnCmdMsg handler looks like this:

BOOL CShowDibView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // for converting hWnd parameters to use m_hWnd member variable HWND hWnd = GetSafeHwnd(); // The following if statement screens out MFC codes that // you don't want to handle here if (nCode != 0) // ignore new MFC requests return FALSE; // old WM_COMMAND code (moved from ShowDib's MenuCommand function // for C++ conversion) BITMAPINFOHEADER bi; HDC hDC; HANDLE h; HBITMAP hbm; HPALETTE hpal; WORD i; CHAR Name[40]; BOOL bSave; INT xSize, ySize, xRes, yRes, dx, dy; RECT Rect; HFILE fh; WORD fFileOptions; UINT id = nID; // replaces id identifier everywhere with value // of nID (for C++ conversion) switch (id) { case IDM_ABOUT: // code omitted in all cases -- see file SHOWDVW.CPP case IDM_COPY: ... case IDM_PASTEPAL: ... case IDM_PRINT: ... case IDM_OPEN: ... case IDM_UPDATECOL: ... case IDM_DIBSCREEN: ... case IDM_ANIMATE0: ... case IDM_STEALCOL: case IDM_ANIMATE5: case IDM_ANIMATE20: ... default: break; } return TRUE; }

After you move the code over, delete the MenuCommand function in SHOWDIB.CPP and delete the WM_COMMAND case in OnCmdMsg.

The code begins with the familiar HWND replacement code, and most of the code consists of the familiar cases for particular command IDs. The following code:

UINT id = nID;

is similar to the HWND replacement. It maps the nID parameter to the id variable used throughout the code.

The following lines screen out unwanted messages:

if (nCode != 0) // ignore new MFC requests return FALSE;

The if statement screens out special MFC handling for tasks like updating toolbar buttons. Your OnCmdMsg doesn't deal with such things, so it's best to short circuit the function when such tasks show up.

As usual, you need to disambiguate Windows API calls in OnCmdMsg, either by prefixing the scope resolution operator as shown earlier in WindowProc or by changing them to equivalent MFC member functions (where possible).


Bypassing MFC's Menu Update Mechanism

Your C-language code undoubtedly contains logic to enable and disable your program's menus depending on the program's current state. In order to make that code work in MFC, you need to disable MFC's mechanism for managing menu-item states.

When the user pulls down a menu, MFC searches for "update handlers" for the items on that menu, using the MFC message map. MFC performs this search before the menu drops down, so by the time the user sees the menu, its items have been appropriately enabled, disabled, checked, or unchecked. If MFC finds a handler for a menu item, it calls the handler to set the menu item's state. If there is no update handler associated with the command ID for a menu item, MFC disables the menu item by default. If you build your application at this stage, before bypassing the MFC mechanism, all of your menus will be grayed.

The following is an example of an update handler from another application:

void CScribDoc::OnUpdateEditClearAll(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(!m_strokeList.IsEmpty());
}

In the example, the update handler accesses an Edit Clear All command through the CCmdUI object passed to it. Calling the object's Enable member function takes care of enabling or disabling any user interface object — such as a menu item or toolbar button — associated with the command ID for that command.

Fortunately, disabling the menu update mechanism is easy. Just add the following line of code to the constructor for class CMainFrame (in MAINFRM.CPP):

CMainFrame::CMainFrame()
{
    m_bAutoMenuEnable = FALSE;    // bypass MFC menu enabling mechanism
}

Now MFC relies entirely on your code to enable menu items. (Do this in CMainFrame rather than the view; the CFrameWnd::m_bAutoMenuEnable variable is easier to access there.)

For SHOWDIB, disable menu updating as shown above.

Note When memory is allocated for a C++ class object, the object's "constructor" is called. The constructor is a special function with the same name as the class. The constructor is normally used to initialize the object's allocated memory.

Just before a C++ object is destroyed, the object's "destructor" is called to clean up. The destructor is often used to deallocate any resources, such as memory, that were allocated by the constructor. The destructor has the same name as the class, prefixed with a tilde (~).

If you don't specify a constructor or destructor in your own classes, C++ supplies default versions, which may or may not do what you want.


Handling Messages Not Sent to the View

If you compile SHOWDIB after all of the previous changes, you'll find that, although the menus work, the menu items are not properly updated. For example, clipboard-related commands are all enabled, and a number of toggle commands at the top of the Options menu should be checked but aren't.

The problem stems from the original decision to override WindowProc in the view class. That was the correct decision, since other steps towards MFC will be much easier than if the override was done in another class, such as the main frame window class. But the view's WindowProc currently contains handler code for the WM_INITMENU message, although, as shown by the Spy++ tool (with Visual C++), the view window never receives that message; the main frame window does, since it owns the menus.

Various approaches are possible (as is often the case under the great flexibility of MFC). The easiest way is to also override WindowProc for class CMainFrame, just to handle this one message case. While it's a bit ugly to have to do multiple function overrides just to get all of SHOWDIB's message handling code to work minimally (you already have OnDraw and OnCmdMsg, as well as the first WindowProc), it's the quickest solution, so it's the one shown here.

For SHOWDIB, override CWnd::WindowProc in class CMainFrame. Then copy all of the code for the WM_INITMENU case into this override.

The CMainFrame::WindowProc override looks like this:

LRESULT CMainFrame::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    // TODO: Add your specialized code here and/or call the base class

    if (message == WM_INITMENU)
    {
        // TODO: Add your message handler code here
        /* check/uncheck menu items depending on state  of related
         * flags
         */

        ::CheckMenuItem((HMENU)wParam, IDM_UPDATECOL,
            (bUpdateColors ? MF_CHECKED : MF_UNCHECKED));
        ::CheckMenuItem((HMENU)wParam, IDM_TRANSPARENT,
            (wTransparent == TRANSPARENT ? MF_CHECKED : MF_UNCHECKED));
        ::CheckMenuItem((HMENU)wParam, IDM_DIBSCREEN,
            (bDIBToDevice ? MF_CHECKED : MF_UNCHECKED));
        ::CheckMenuItem((HMENU)wParam, IDM_NOUGLY,
            (bNoUgly ? MF_CHECKED : MF_UNCHECKED));
        ::CheckMenuItem((HMENU)wParam, IDM_MEMORYDIB, MF_CHECKED);
        ::EnableMenuItem((HMENU)wParam, IDM_PASTEDIB,
            ::IsClipboardFormatAvailable(CF_DIB)?MF_ENABLED:MF_GRAYED);
        ::EnableMenuItem((HMENU)wParam, IDM_PASTEDDB,
          ::IsClipboardFormatAvailable(CF_BITMAP)?MF_ENABLED:MF_GRAYED);
        ::EnableMenuItem((HMENU)wParam, IDM_PASTEPAL,
         ::IsClipboardFormatAvailable(CF_PALETTE)?MF_ENABLED:MF_GRAYED);
        ::EnableMenuItem((HMENU)wParam, IDM_PRINT,
            bLegitDraw ? MF_ENABLED : MF_GRAYED);
        ::EnableMenuItem((HMENU)wParam, IDM_SAVE,
            bLegitDraw ? MF_ENABLED : MF_GRAYED);
        ::EnableMenuItem((HMENU)wParam, IDM_COPY,
            bLegitDraw ? MF_ENABLED : MF_GRAYED);

        ::EnableMenuItem((HMENU)wParam, IDM_ANIMATE0,
            bLegitDraw ? MF_ENABLED : MF_GRAYED);
        ::EnableMenuItem((HMENU)wParam, IDM_ANIMATE5,
            bLegitDraw ? MF_ENABLED : MF_GRAYED);
        ::EnableMenuItem((HMENU)wParam, IDM_ANIMATE50,
            bLegitDraw ? MF_ENABLED : MF_GRAYED);
        ::EnableMenuItem((HMENU)wParam, IDM_ANIMATE100,
            bLegitDraw ? MF_ENABLED : MF_GRAYED);
        ::EnableMenuItem((HMENU)wParam, IDM_ANIMATE200,
            bLegitDraw ? MF_ENABLED : MF_GRAYED);
        ::EnableMenuItem((HMENU)wParam, IDM_ANIMATE201,
            bLegitDraw ? MF_ENABLED : MF_GRAYED);
        ::EnableMenuItem((HMENU)wParam, IDM_STEALCOL,
            bLegitDraw ? MF_ENABLED : MF_GRAYED);
    }
    return CFrameWnd::WindowProc(message, wParam, lParam);
}

Notice that the Windows API calls have been disambiguated with the scope resolution operator. There's no need for calls to GetSafeHwnd or GetSafeHdc because none of the function calls pass an HWND or an HDC. You also need the following include directive in file MAINFRM.CPP:

#include "showdib.h"

This approach preserves your investment in the original SHOWDIB code and takes only a moment using ClassWizard and a bit of cut and paste. The better, but more time-consuming, alternative is to go all the way and use MFC's mechanism for updating menu commands, as described in Using MFC Menu Update Handlers.


Checking for Phase 1 Leftovers

If you did everything correctly in Phase 1, the only leftover issues are possibly the following:

  • You may have code elsewhere than in your WinMain function that calls RegisterClass to register window classes (the standard Windows variety, not to be confused with C++ classes).
  • You may have calls to other functions not used in MFC. The principal one of these is DefWindowProc, which you don't normally call from an MFC application.
  • You should have no remaining references to the BeginPaint or EndPaint Windows API functions. See Moving Your WM_PAINT Code Into the View.

Window Registration Calls Outside WinMain

You can search your original C source files for calls to RegisterClass. If any of these calls were for windows that now are equivalent to the CMainFrame and view classes, these calls should have gone away. Otherwise, you can leave them as they are. After all, migration to C++ focuses on those parts of your application that have counterparts in the MFC framework, such as the main frame window and the view (client area). Much of your code should need no further migration, aside from converting C to C++ syntax. For example, if you have a child window that is now a child window of the view (such as a control window of your own) you shouldn't need to do anything to the code for that window, including its WndProc.

Calls to DefWindowProc and Other Non-MFC Functions

Once you've turned your application into an MFC application, you don't need to call DefWindowProc yourself. MFC's message map mechanism ensures that MFC handles any messages that you don't handle.

As you'll see later, in Using the MFC Message Map, MFC applications don't usually use WindowProc and a giant switch statement to handle Windows-based messages. Instead, message handling code is normally placed in separate message handler functions (one per message). MFC calls these class member functions through their class's message map. When you create a handler function for a message, your handler overrides a version defined in your class's MFC base class. Instead of calling DefWindowProc to handle messages that you don't handle, you typically call the base-class version of the handler function, something like this:

void CShowDibView::OnTimer(UINT nIDEvent)
{
    // your code to handle WM_TIMER

    // then call the base-class version of OnTimer
    CView::OnTimer(nIDEvent);
}

Notice that your WindowProc function calls CView::WindowProc at the end. In a message handler such as OnTimer, this gives MFC a chance to do its own processing (if any) for the message. In the case of WindowProc, calling the base class lets MFC handle any unhandled messages.


Your C Application is Now an MFC-Based Application

After making the changes in the previous sections, build your application and fix any errors. When you finish, you have successfully converted your C-language application for Windows into a C++ application using MFC.

If you like, you can actually stop at this point. But, as Phase 2 shows, a little more work will greatly tighten the integration between your code and the MFC framework.


Phase 2: Get Rid of Your Message-Handling Switch Statements

You now have an MFC C++ application created from your existing code base. The application already makes use of MFC to do more than it could originally, depending on the options you chose in AppWizard. But so far the application is not very tightly integrated with MFC; as a result, there are many MFC features it still can't take advantage of.

Note Phases 2 and 3 are optional. Consider them steps that add significant value to your application but that aren't strictly necessary. These steps have not been taken in the SHOWDIB sample application. Most are illustrated in the Scribble tutorial and other Visual C++ documentation and samples. See your C++ documentation.

In Phase 2, you'll continue to exploit the class structure of MFC, and you'll make use of the MFC message map mechanism. At the end of Phase 1, there are two large switch statements left in your migrated application. One is in WindowProc. The other is in OnCmdMsg. The goal of Phase 2 is to systematically remove both the WindowProc and OnCmdMsg function overrides, replacing them with message-mapped calls to class member functions designed to handle specific Windows-based messages.

Phase 2 steps include:

1. Using the MFC message map to handle Windows-based messages.

2. Using MFC's menu update mechanism to update the state of menus (and other user interface objects, such as toolbar buttons).

3. Using class member variables to replace using the cbWndExtra and cbClsExtra members of the WNDCLASS structure, in which Windows-based programmers usually store application-specific information.


Using the MFC Message Map

In MFC, Windows-based messages can be sent to any window, including a view. Additionally, as described in Moving WM_COMMAND Code Into the View. MFC can route WM_COMMAND messages not only to window objects but also to documents and other "command target" objects. Windows-based WM_ messages arrive at their destinations in the usual way, but WM_COMMAND messages from menus, toolbar buttons, and accelerators follow a default routing from one object in the running application to another, until one of the objects claims the command and responds to it.

All of this ¾ both Windows-based message sending and command routing ¾ operates through MFC's "message map" mechanism. Message maps provide a table-based mechanism for connecting a particular message to a handler function designated to respond to that message. This mechanism is more efficient than C++ virtual functions, given the large number of Windows-based messages. In this way, MFC wraps messages just as it wraps APIs.

When an MFC object receives a message, it searches its message map (and the message maps of any classes it is derived from). If it finds a handler for the message, the object calls the handler.

A typical message map (located in the .CPP file for its class) looks something like this:

BEGIN_MESSAGE_MAP(CShowDibView, CView)
    //{{AFX_MSG_MAP(CShowDibView)
    ON_WM_TIMER()
    ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
    //}}AFX_MSG_MAP
    // Standard printing commands
    ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()

Between the beginning and ending macros, the message map contains one entry per mapped message. Each entry consists of an MFC macro with zero or more arguments. Note the entries for WM_TIMER and the first ON_COMMAND; you'll see example message handlers for these entries below. Entries you make via ClassWizard are placed between bracketing comments: "//{{AFX_MSG_MAP" and "//}}AFX_MSG_MAP." Entries made without ClassWizard should be added outside the comments.

A valuable additional migration step, then, is to move some or all of your message handling code into MFC message handlers. You use ClassWizard to create the handlers. The general process is simple:

Þ To move message handling code to an MFC message handler

1. Open ClassWizard, select the class you want to create a handler in, and specify the message to map.

ClassWizard creates a prototype for the handler function in the class declaration. It also creates the shell of the handler function, complete with the correct parameter signature.

2. Move your code for that message into the handler and do a little fix up, as illustrated for SHOWDIB below.

Class CWnd contains a member function for each Windows message. The names of these functions begin with "On" and provide default handling for the messages. When you create a new handler of the same name in one of your classes, the new handler overrides the default handler as if it were a virtual function (although in fact it is a message-mapped function instead).

For SHOWDIB, consider this example, a handler for the WM_TIMER message:

void CShowDibView::OnTimer(UINT nIDEvent)
{
    // TODO: Add your message handler code here and/or call default
    HWND hWnd = GetSafeHwnd();

    /* Signal for palette animation */
    hDC = ::GetDC(hWnd);
    hOldPal = ::SelectPalette(hDC, hpalCurrent, 0);
    {
        PALETTEENTRY peTemp;

        /* Shift all palette entries left by one position and wrap
         * around the first entry
         */
        peTemp = pLogPal->palPalEntry[0];
        for (i = 0; i < (pLogPal->palNumEntries - 1); i++)
            pLogPal->palPalEntry[i] = pLogPal->palPalEntry[i+1];
        pLogPal->palPalEntry[i] = peTemp;
    }
    /* Replace entries in logical palette with new entries*/
    ::AnimatePalette(hpalCurrent, 0, pLogPal->palNumEntries,
                                              pLogPal->palPalEntry);

    ::SelectPalette(hDC, hOldPal, 0);
    ::ReleaseDC(hWnd, hDC);

    /* Decrement animation count and terminate animation
     * if it reaches zero
     */
    if (!(--nAnimating))
        ::PostMessage(hWnd,WM_COMMAND,IDM_ANIMATE0,0L);

    CView::OnTimer(nIDEvent);
}

The code moves over smoothly. The only things to clean up at this point are deleting the case and break statements, disambiguating Windows API calls, calling GetSafeHwnd, and making sure the variables and functions are visible within the chosen class — in this case the view class. At the bottom, note the additional call to the base class's version of OnTimer, to allow the base class to do any additional WM_TIMER processing it needs to do.

One important issue arises: how do you decide which class to map a message to? First, keep in mind that Windows-based messages other than WM_COMMAND must go to CWnd-derived objects. Second, use this rule of thumb — map each message to the class with the most contextual information for handling it. You probably want to handle commands related to the document as a whole, such as Save, in the document. You probably want to handle commands relating to selecting and editing data in the view. Commands that relate to the whole application probably should map to the application object. If handling a message in object A would require accessing lots of information in object B, maybe object B should handle that message.

Here is an additional example for SHOWDIB, mapping a WM_COMMAND message to the view object. The command is Edit Copy (related to selection), which generates a command whose ID is ID_EDIT_COPY:

void CShowDibView::OnEditCopy()
{
    // TODO: Add your command handler code here
    HWND hWnd = GetSafeHwnd();

    // if (!bLegitDraw)     // lines removed
        // return 0L;

    /* Clean clipboard of contents */
    if (bLegitDraw && ::OpenClipboard(hWnd)) {
        EmptyClipboard ();
        SetClipboardData (CF_DIB     ,NULL);
        SetClipboardData (CF_BITMAP  ,NULL);
        SetClipboardData (CF_PALETTE ,NULL);
        CloseClipboard ();
    }
}

Again the code maps over very cleanly. OpenClipboard is the only clipboard function in Windows that MFC wraps, so it's the only one you need to disambiguate. You only need to make sure the bLegitDraw variable is visible and call GetSafeHwnd to use m_hWnd for the HWND parameter to OpenClipboard. One last change: because OnEditCopy doesn't return a value, check the bLegitDraw flag in the second if statement, as shown above, and delete the first if statement, now commented out.

Tip If you selected the Toolbar option in AppWizard, the default Copy button on the toolbar is enabled at this point. The button is associated with the same ID as the menu item; the same handler, OnEditCopy, handles both user interface objects.

Look again at the message map entries for the WM_TIMER and WM_COMMAND messages:

ON_WM_TIMER() ON_COMMAND(ID_EDIT_COPY, OnEditCopy)

The ON_WM_TIMER macro takes no parameters. For ON_COMMAND, though, it's necessary to specify which command ID is being handled and which handler function will do the job.

Using this technique with ClassWizard, you can easily map all of the cases in your WindowProc and OnCmdMsg functions to separate message handlers in a short time. Don't forget to delete WindowProc and OnCmdMsg when you finish mapping the last message. You'll take care of the extra WindowProc override in class CMainFrame (which you created in Handling Messages Not Sent to the View) in Using MFC Menu Update Handlers.

Tip In Visual C++ 2.0, use ClassWizard to delete the function when you've emptied it. In earlier versions of Visual C++, or other C++ products, you may need to do it by hand. If so, delete (a) the function declaration in the .H file for your view class, and (b) the function definition in the .CPP file.


Using MFC Menu Update Handlers

Your application needs to enable and disable menu items at appropriate times to let the user know which commands are available in any given context. In C-language applications for Windows, it's fairly typical to update the state of all menu items in a single message handler. For example, in response to the WM_INITMENU message, SHOWDIB executes eighteen lines like the following line for the Edit Copy menu item:

... EnableMenuItem(wParam, IDM_COPY, bLegitDraw ? MF_ENABLED : MF_GRAYED); ...

You saw this code in Handling Messages Not Sent to the View. Except for a difference in command IDs, this code can be replaced by an MFC update handler like the following:

void CScribDoc::OnUpdateEditCopy(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(bLegitDraw ? MF_ENABLED : MF_GRAYED);
}

The code uses the same Boolean condition as the C version, but it makes the Boolean test in a call to the Enable member function of a CCmdUI object that MFC has attached to the ID_EDIT_COPY command. The bLegitDraw variable must be visible.

Tip In MFC version 3.0, you can update multiple command IDs with a single handler by specifying a range of contiguous IDs. For more information, see the article "Message Maps: Ranges of Messages" in Part 2 of Programming with the Microsoft Foundation Class Library (Visual C++ 2.0).

The MFC approach lets the framework manage the timing and complexity of keeping menus up to date. Although you now won't be able to find all such code in one place, you can easily get to the appropriate update handler for a command through ClassWizard's Edit Code button. The greatest advantage is that the same update handler that updates the menu item also updates the corresponding toolbar button if you have one. This works because both the menu item and the toolbar button have the same command ID.

Þ To add an update handler

1. Open ClassWizard, select the class you want to create a handler in, and specify the command ID that the handler is to update.

ClassWizard creates a prototype for the handler function in the class declaration. It also creates the shell of the handler function, complete with the correct parameter signature.

2. Move your code for that menu command into the handler.

In most cases, you can use the same Boolean expression you used when calling EnableMenuItem.

For SHOWDIB, you would move the code now found in CMainFrame::WindowProc into update handlers, then delete the WindowProc function in CMainFrame.

Note When you begin to update menus this way, remove the line of code that disables the MFC update mechanism in CFrameWnd::CFrameWnd (MAINFRM.CPP):

CMainFrame::CMainFrame()
{
// m_bAutoMenuEnable = FALSE; // bypass MFC menu enabling mechanism
}


Replacing Your Use of cbWndExtra and cbClsExtra

Programmers who write Windows-based applications in C are familiar with storing extra data in the cbWndExtra and cbClsExtra fields of the WNDCLASS structure. When you register your own window class in WinMain, you can reserve extra storage space for your own use. Programmers often use this space to store pointers to their own data structures.

In MFC applications, this practice is unnecessary. The HWND for a window is encapsulated by a CWnd object. If you need extra storage, just add your own member variables to CMainFrame or other CWnd-derived classes.

Note Using the class member variable approach is required unless your view or frame window classes register their own window classes. Normally, MFC registers standard window classes for you, so you don't get an opportunity to use cbWndExtra or cbClsExtra.

SHOWDIB doesn't use cbWndExtra and cbClsExtra. It simply sets the fields to zero, so new CWnd member variables aren't necessary.


Checking for Phase 2 Leftovers

If you did everything correctly in Phase 2, the only leftover issue is that if you chose to move all message handling code into message handler functions, you should have deleted WindowProc and OnCmdMsg from your view class entirely. If you also created menu update handlers, WindowProc should be gone from class CMainFrame as well.


Phase 3: Integrate Further with MFC

Even after Phases 1 and 2 there are still many things you can do to improve your application's integration with MFC and further take advantage of MFC and C++. Phase 3 discusses some of the following additional steps you can take:

  • Make GDI calls the MFC way.
  • Hook up some of MFC's diagnostic features, such as the Dump and AssertValid functions.
  • Convert some of your C data structures to C++ classes.
  • Move data structures into the MFC document class.
  • Use MFC's serialization protocol to replace some or all of your present file-handling code.
  • Move your printing code into the view's OnDraw function.
  • Use a different view class such as CScrollView or CFormView, or use a dialog box to implement your application's form-based user interface.
  • Replace the use of cbWndExtra and cbClsExtra with class member variables.

In general, the items in this list will simplify your code, relying on MFC to supply most of the standard wiring to Windows that you must do over and over in C, and taking advantage of many MFC features that hide complexity.


Make GDI Calls the MFC Way

In your original Windows code, the device context appears as a handle to a GDI data structure (a DC). Certain Windows API functions — the GDI calls — operate on a device context when you pass them a handle to one. In contrast, MFC encapsulates the device-context handle (HDC) inside an MFC object of class CDC. Using the device context the MFC way requires a different syntax and a different mode of thinking.

The model just described is one example of a general model in MFC, which comes from the use of C++. MFC encapsulates common Windows-based entities — device contexts, windows, dialog boxes, GDI objects such as brushes — in MFC classes. To use these classes, you (a) create an object of the class (or MFC does) and (b) call member functions of the class to manipulate it.

Making GDI calls the MFC way occurs mainly in the view's OnDraw member function. Making other Windows API calls the MFC way occurs throughout the MFC portions of your program.

In Moving Your Paint Code to OnDraw, you saw SHOWDIB's WM_PAINT code as it appears in the OnDraw member function override. That code doesn't take full advantage of MFC, though, since it ignores the pointer to a CDC object that OnDraw receives as a parameter:

void CShowDibView::OnDraw(CDC* pDC)

That CDC object encapsulates a completely prepared device context, but instead of using it, the OnDraw code gets its own device context:

hDC = ::BeginPaint(hWnd, &ps);

That version of OnDraw also calls Windows API functions directly rather than calling the MFC equivalents, requiring use of the C++ scope resolution operator to disambiguate the calls.

A New OnDraw

Here's a version of OnDraw that (a) uses the CDC pointer and (b) calls MFC versions of the Windows API functions:

void CShowDibView::OnDraw(CDC* pDC)
{
    HWND hWnd = GetSafeHwnd(); // for converting hWnd parameters
    HDC hDC = pDC->GetSafeHdc(); // for converting hdc parameters

    // TODO: add draw code for native data here
    /* If we have updated more than once, the rest of our
    * window is not in some level of degradation worse than
    * our redraw...  we need to redraw the whole area
    */
    if (UpdateCount > 1) {
        ValidateRect(NULL);   // replaces Begin/EndPaint calls
        UpdateCount = 0;
        InvalidateRect(NULL);
        return;    // replace break statement
    }

    // BeginPaint call deleted
    AppPaint(hWnd,
             hdc,
             GetScrollPos(SB_HORZ),
             GetScrollPos(SB_VERT) );
    // EndPaint call deleted
    // delete break statement here

}

In the second line of the function, this version of OnDraw calls GetSafeHdc (similar to GetSafeHwnd) through the pDC pointer. Recall that this syntax resembles accessing a struct's members through a pointer, but here the notation is used to access and call a member function of a C++ object instead. After this call, a safe HDC is available for use in the call to the SHOWDIB application-specific function AppPaint.

Instead of disambiguating the direct calls to the Windows API functions, as you saw earlier in WindowProc, this version of OnDraw uses the MFC versions of the same functions. Because the view class is ultimately derived from class CWnd, it inherits all of CWnd's member functions, including these. OnDraw, as a member of the view class, can simply call those inherited functions directly (without having to call through a pointer as with the HDC). Note that the MFC versions don't pass an HWND parameter and so are shorter and simpler.

You'll want to make such changes generally in your new MFC application, although you really only need to do so in the code associated with your derived MFC classes. Such changes aren't necessary in the original C code that you simply converted to C++.


Hook Up MFC Diagnostics

Since most MFC classes are ultimately derived from class CObject, they inherit several useful diagnostic features. The two most important of these are the ability to:

  • Dump the contents of a class object for diagnostic analysis.
  • Have an object validate its own internal state before you use it.

You can easily take advantage of these abilities in your documents, views, and other classes derived from MFC base classes. All classes derived from CObject, directly or indirectly, inherit — and can override — two member functions: Dump and AssertValid. AppWizard assumes that you'll want to make these overrides and so writes shells for the functions for you.

Dumping an Object's Contents

During development, you'll often want to know what an object's member variables contain. Override the Dump member function in your derived class to dump the class's contents to a "dump context." (Consult the discussion of diagnostics in your documentation to see where dumped contents are sent. You can search for the afxDump global variable.)

For example, even before you begin to move SHOWDIB's code into the document and view classes, you can override CShowDibView::Dump, something like this:

#ifdef _DEBUG
void CShowDibView::Dump(CDumpContext& dc) const
{
    CView::Dump(dc);

    // dump view object contents
    dc << member_variable_1;   // pass variables to dump context
    dc << member_variable_2;
    member_variable_3.Dump();  // or call the Dump function of any
                               // class objects contained by your class
    //...
}
#endif //_DEBUG

Notice that Dump can only dump variables visible to it. Local variables inside member functions of CShowDibView aren't visible. But member variables of the class are visible. If a variable constitutes part of what you consider the class's state, it's a good idea to make it a class member variable.

Dump is always conditionally compiled for debug builds. MFC passes you a CDumpContext object, which works something like a C++ iostream object like cout. You "insert" variables to the dump context with the CDumpContext insertion operator (<<); insertions can be chained. CDumpContext accepts many common data types. You can regulate the "depth" to which objects are dumped. If you want to dump object-type data with its own Dump function, you can call the object's Dump function, passing your CDumpContext object. For more information about dumping, see the article "Diagnostics" in Programming with the Microsoft Foundation Class Library (Visual C++ 2.0) or Chapter 15 in the Class Library User's Guide (earlier versions).

Validating an Object's State

C++ class objects can be internally very complex. When you construct an object, or receive one as a parameter to a function, you'd like to be able to validate the object's internal state for some sort of consistency or reasonableness. Override the AssertValid member function in your derived class to enable objects of the class to report their own internal validity. Used consistently throughout your CObject-derived classes, AssertValid is a powerful diagnostic tool.

In your AssertValid override, you usually use the ASSERT macro to test assumptions about the class's member variables. In debug builds, ASSERT tests a Boolean condition. If the condition is FALSE, the macro terminates the application with a message. Otherwise, execution continues normally. In release builds, ASSERT does nothing (and arguments passed to it are not evaluated). For example, an AssertValid function might look like this:

#ifdef _DEBUG
void CShowDibView::AssertValid() const
{
    // assert validity of base class contents
    CShowDib::AssertValid();

    // assert the validity of object contents
    ASSERT(m_nValue == 0);
    ASSERT_VALID(m_pSubObject);
    // ...
}
#endif //_DEBUG

Like Dump, AssertValid is conditionally compiled for debug builds. The function first calls the base class version to test any variables declared in that or higher base classes. Then it tests the validity of the derived class's member variables.

Tip If you need ASSERT functionality in a release build, use the VERIFY macro instead.

When one of your functions is passed an object that overrides AssertValid, use the ASSERT_VALID macro to invoke the object's AssertValid function:

... ASSERT_VALID(pAnObject); // calls pAnObject's AssertValid ...

If your class has member variables that contain pointers to other objects, you can also use ASSERT_VALID inside your AssertValid function, as shown above, to validate those objects. An ASSERT_VALID call can often work its way down through several layers of the AssertValid functions of embedded or contained objects. Figure 9 shows this process for the example that follows.

For example, suppose you have a CObList collection containing pointers to CPerson objects, and class CPerson, like CObList, overrides AssertValid. You can use this AssertValid function to check the internal consistency of the list. With a little more work, you can even have your list (a CPersonList class derived from CObList) call the AssertValid function of each contained CPerson object. The CPerson objects might in their turn call the AssertValid functions of subordinate objects that they contain. Here's a small fragment of code that gets the whole process going:

// Given a pointer, pList, to a CPersonList of CPerson pointers...
ASSERT_VALID(pList);
// The rest flows from that call

In a debug build, you aren't worried about the overhead of all this validation, and the easy validation is very valuable diagnostic information. For more information, see the article "Diagnostics: The ASSERT Macro" in Part 2 of Programming with the Microsoft Foundation Class Library for Visual C++ 2.0, or for earlier versions see "Overriding the AssertValid Function" in the Class Library User's Guide.


Convert C Data Structures to C++ Classes

The C language provides only a few simple data structures, so that implementing complex data structures usually means defining a C struct and some functions to operate on it. Unfortunately, the actual data structure and the functions for manipulating it seldom have any obvious or binding connection, except perhaps a naming scheme and placement in the same C module.

C++ is richer in data structures, primarily by supplying classes ¾ which define real types. A class is a language-supported means of encapsulating both the data and the functions for accessing it within a single well-defined object. The encapsulation is real, too; by using the public, private, and protected access specifiers, you can completely control all access to the data.

The most important way to leverage C++ classes for data structures is to encapsulate the structures themselves inside classes, along with the functions to manipulate the structures. For example, SHOWDIB.H declares a group of functions for operating on a DIB: OpenDIB, WriteDIB, DibNumColors, and so on. MFC's class CBitmap supports ordinary bitmaps, not DIBs, but you could write a class CDIB, embedding a handle to a DIB and encapsulating the loose functions now declared in SHOWDIB.H.

Class encapsulation also lets you clean up your header files in a variety of ways, such as encapsulating loose collections of global variables or #define directives inside a class. For example, SHOWDIB defines the following flags in SHOWDIB.H for its FileOpen function (comments removed):

#define OF_MUSTEXIST   0x00010000
#define OF_NOSHOWSPEC  0x00020000
#define OF_SHOWSPEC    0x00000000
#define OF_SAVE        0x00040000
#define OF_OPEN        0x00080000
#define OF_NOOPTIONS   0x00100000

In C++, you might declare an enumerated type (with more readable mixed-case names) within the scope of a file-handling class, perhaps derived from the MFC CFile class:

class CMyFileClass : public CFile
{
...
    enum FileFlags
    {
    MustExist = 0x00010000  // file must exist if user hits Ok
    NoShowSpec = 0x00020000  // DO NOT Show search spec in edit box
    ShowSpec = 0x00000000  // Show search spec in edit box
    Save = 0x00040000  // Ok button will say "Save"
    Open = 0x00080000  // Ok button will say "Open"
    NoOptions = 0x00100000  // Disable the options fold out
    };

    // ...
};

From outside the class, when you pass file flags as function arguments, you specify the encapsulated flag name with a qualifier:

OpenFile( ..., CMyFileClass::ShowSpec, ... );


Move Data Structures into Your Document Class

In MFC's document/view architecture, documents store data and views display it. To take fuller advantage of this architecture (and of document serialization, covered later), move important data structures into the CDocument-derived class that AppWizard creates for you.

To move data into the document, create document-class member variables of the appropriate types. For example, suppose your program stores text data in an array. Create a document member variable, perhaps either a CString or one of MFC's array-type collection classes. Add member functions as needed to get and set the data. The following abbreviated example shows what one data structure (using a CObList collection object) looks like, along with several member functions to access the data.

class CPersonDoc : public CDocument
{
    // ...
    // Attributes
    CObList    plistPersons;  // see CObList in Class Library Reference
    CPerson* GetPerson(UINT nPersonID);
    // Operations
    BOOL AddPerson(CPerson* pPerson);
    BOOL DeletePerson(UINT nPersonID);
    // ...
};

In SHOWDIB, the most obvious data structure to move to the document is the DIB structure. You could create the appropriate DIB handle and store it in the document. Along with the data structure, move over all functions that act on the DIB. If you've already encapsulated the DIB handle within a CDIB class, that class will have the basic DIB-manipulating functions, and the document can store a CDIB or a pointer to a CDIB.


Use Document Serialization for Input/Output

MFC provides protocols for many standard commands and full implementations for some of them. For example, most of the File Open command is implemented for you. In response to File Open, the MFC framework displays the File Open dialog box, retrieves the selected filename, creates a document object and its associated view and frame window objects, opens the disk file, and tells the document to read the file in. All you have to write is a small amount of document-class code to specify what to read. Correspondingly, MFC documents respond to the Save and Save As commands. All you have to write is the code to write data to a file that the framework opens and closes for you.

Figure 10 and Figure 11 outline the File Open and File Save command sequences, which use file serialization.

MFC's default implementation of these commands is called "document serialization." MFC calls a document object's Serialize member function to read or to write the document's data serially to or from a disk file. To make serialization really useful in an object-oriented environment, MFC makes it easy to store not only built-in C++ data types but also objects, including many of those derived from class CObject. Using this default "object persistence" mechanism, you can read and write the contents of objects, storing them on disk to be read back in later and reconstituted to their former states.

For example, suppose your document contains an array of CPerson objects, where each object contains a set of information about one person: CString objects containing the person's name and address, a CTime object containing date of birth, and perhaps a CPhone object, a WORD storing a social security number, and so on. Your program lets the user create several new person objects, storing them in the array.

When the user chooses the File Save command, MFC calls your document's Serialize member function, as shown in Figure 11. Serialize is passed a pointer to a CArchive object, which encapsulates a pointer to a CFile object already set to the right filename and already opened on disk. Within Serialize, you iterate through the person list, calling CPerson::Serialize for each person in the list. That function, in turn, passes the CPerson object's data to the CArchive, much as variables are passed to the Dump function described earlier. The CPerson object passes its name, address, and other information one by one to the archive, which causes them to be written to the file. A similar process occurs when you read the data back in.

On disk, a serialized object consists of a sequence of data items: some "header" information to identify the type of data, then the data itself. When your program "deserializes" an object, it determines its type, dynamically constructs an empty object of the correct type, and tells the object to read in its own data.

To do your simple part of this complex dance, move your data structures into the document class and then override Serialize in your document class (actually, AppWizard writes the empty shell of this override for you). Here's a simple example of a CPerson::Serialize member function that the document's Serialize function can call:

void CPerson::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        // insert objects in the archive
        ar << m_strName;
        ar << m_wAge;
        ar << m_wSSN;
    }
    else
    {
        // extract objects from the archive
        ar >> m_strName;
        ar >> m_wAge;
        ar >> m_wSSN;
    }
}

You write the lines within the if and else blocks. Store data in the archive using the CArchive insertion operator (<<), which is overloaded for most common built-in and MFC data types. Load data with the extraction operator (>>). Notice that you store and load data items in the same order.

If serialization doesn't suit your needs, you can override various member functions of the document class or the application class to implement something else.

For example, SHOWDIB already has file-handling code, so you might incorporate it into an overridden version of the serialization mechanism to take advantage of MFC's existing File Open and Save implementations while still using parts of SHOWDIB's existing code. One approach is to override CDocument::OnOpenDocument and remove the code that creates an archive and calls Serialize, replacing it with SHOWDIB's I/O code.


Moving Your Printing Code to MFC

CView::OnDraw has a second very important purpose in MFC besides housing your painting code. The framework implements both printing and print preview using your view's OnDraw function. The basic idea is that in MFC you simply draw to the current device — sometimes it's the display, sometimes it's a printer, and sometimes it's a printer emulation on the display (print preview).

Because of this, the File Print command is mapped to class CView, which does all of the necessary setup work and then calls OnDraw to render the image using the device context object passed to it. For drawing on the display, the CDC pointer passed is for the display. For drawing on a printer, the CDC is for the current printer. And for drawing a scaled image in a print preview window, the CDC that is passed wraps two device contexts. One represents the display; the other represents the printer. Using both device contexts, drawing output in OnDraw emulates printer output in the print preview window to approximate the output you could expect to see on the printer.

When you move your printing code into OnDraw, you get to abandon much of it. MFC takes care of displaying File Print and File Print Setup dialog boxes. It also manages the print loop in large measure. Most of what you keep is the actual GDI code that renders the image on the printer.

As shown in Figure 12, when it's time to print a page, MFC calls your view's OnPrepareDC and OnPrint member functions, passing the number of the page to print (page counts start at 1). OnPrepareDC makes adjustments to the device context before drawing takes place. OnPrint calls OnDraw, passing along its device context pointer, and OnDraw draws the current page. For simple printing, all of this takes place with no intervention from you. But if you are doing multiple-page printing, printing headers and/or footers, and the like, you'll need to override OnPrepareDC and/or OnPrint.

If you need to make your own adjustments to the device context before printing a page, override OnPrepareDC in your view class. You might, for instance, move the viewport origin and the clipping region to ensure that the appropriate portion of the document gets printed. For multiple-page documents, this is how you set up to print specified pages, placing the viewport origin at the calculated upper left corner of the page within your document.

Override OnPrint to perform any rendering that should be done only during printing and not for screen display. For example, you can draw headers and footers in OnPrint before (and after) OnDraw renders the rest of the page.

If your application is not WYSIWYG ("What you see is what you get") — that is, if what you print differs significantly from what you display on the screen — you can separate the print-only logic from the display logic in OnDraw, perhaps like this:

...
if (IsPrinting)    // another CView member function
{
    // Do print-only drawing
}
else
    // Do display drawing
}

Or you can do all of your print-only drawing in your OnPrint override without calling OnDraw from there. In this way, OnDraw is called only for screen display, while OnPrint is called only for printing and print preview.

SHOWDIB's printing logic (not shown in this guide or the samples) is necessarily separate from its screen display logic, so you would avoid calling OnDraw from OnPrint. This is because you display a bitmap on screen all at once, but you print it in "bands" or strips. Screen display is handled in OnDraw, while printing is handled in OnPrint. This is the exceptional case in which the two processes cannot be independent of the device being used. Fortunately, a fair amount of the printing code (involving dialog boxes and some parts of the printing loop) disappears under MFC due to all of the things the framework does for you.


Use a Different View Class

The discussion in this migration guide assumes that you use the default CView-derived view class in your migration. That's appropriate for SHOWDIB, for instance. But MFC supplies several other view classes, and view/frame window configurations, that may be more suitable for your program:

  • Use CScrollView to have MFC manage the scrolling for you.

    You'll need to strip out your old scrolling code, so CScrollView may be more suitable for new applications than for migrating.

  • Use CFormView (or CRecordView) if your application needs a form-based user interface. You can also use a dialog box.

    All of these user interface options are based on dialog-template resources. CFormView and CDialog are alternative ways to implement a dialog-like user interface with controls. CRecordView is best for database access applications that display data from a table.

  • Use CEditView if you need a simple text editor view.

    CEditView is really a Windows-based edit control with some special capabilities, including file serialization and clipboard support.

  • Use multiple view classes, either in a splitter window or in separate frame windows, if your program requires a complex user interface.

    While most of these configurations are not supplied in single classes, they're all relatively easy to implement using class CSplitterWnd and/or class CDocTemplate. See the documentation and third-party books such as David J. Kruglinski's Inside Visual C++ (2nd Ed., Microsoft Press, 1994).

  • Use MDI instead of SDI.

    AppWizard will create an MDI application for you. If different child frame windows are to hold different kinds of documents (such as text vs. graphics), you'll need to implement a view and a document class for each kind.

You can specify many of these options in AppWizard at the outset. You can also in some cases add the options after the fact. For example, making a few simple code changes converts a CView into a CScrollView.


Conclusion: Know Your Framework

If you've followed the general migration discussion in the first Phase of this migration guide, and perhaps tried out some of the additional migration techniques in the second and third Phases, you have an MFC application whose heart is essentially still the original code you wrote in C. But now the application is in C++, and because it's an MFC application, it's poised for easier future development and future migrations, such as from 16 bit to 32 bit.

At this point, you may still find the MFC application framework somewhat mysterious. This migration guide concludes with a few tips about how to improve your understanding and take advantage of MFC's tremendous flexibility.


You Can Understand MFC's Structure

The MFC framework, and therefore any MFC application, has a very rational structure. MFC classes encapsulate key abstractions: the application as a whole, documents, frame windows, views of a document through a frame window's client area, message maps, dialog boxes, and GDI objects.

A running MFC application is a collection of communicating objects based on these abstractions. An object is essentially a different kind of code module whose functions you can call and whose variables you can access (unless they're protected or private). There's still a WinMain function that registers window classes and operates a message pump, although WinMain is hidden within MFC and designed so you don't normally have to touch it. Windows-based messages are still sent to windows, but the messages are handled differently. And MFC provides default (but sometimes very complete) implementations of common menu commands, such as File Open, and clean protocols for implementing other commands, such as the clipboard commands on the Edit menu.

For an overview of MFC's structure, see the hierarchy chart in the Class Library Reference or your C++ documentation.


Study the MFC Source Files

Study the files that AppWizard creates. At the same time, look at the MFC base class of each class that AppWizard creates. If you understand C++ inheritance, this will begin to give you a sense of how much implementation lurks behind the relatively simple sources put out by the wizard and where things are located in those sources.

The MFC source files are available in the \MFC\INCLUDE and \MFC\SRC directories relative to your C++ installation. The two most important files are AFX.H and AFXWIN.H. Together, these files declare most of the MFC classes and outline the structure of MFC. When you build an MFC application, you aren't building only the few files that AppWizard creates in your project directory; you're pulling in a large amount of well-designed, well-tested MFC code as well.


Learn the Pressure Points

MFC is a "skeleton" application (with some flesh already on the bones). To customize this skeleton with your own code, you must learn where to attach your code to the framework. Perhaps you're familiar with the notion of "pressure points" on the human body; if a person is bleeding, you locate the appropriate pressure point, apply pressure there, and the bleeding stops. By analogy, MFC has pressure points too.

Every bit of functionality already built into MFC is located in one or more member functions of one or more classes. To modify the default functionality, you must locate the appropriate function(s), override them in a derived class, and supply your own functionality.

For example, if you want to do your own input/output rather than relying on MFC serialization, you can override the framework in several ways, as described in Use Document Serialization for Input/Output.Determining which functions to override can be difficult. The better you understand how MFC does what it does, how each class works, and what the major subsystems and architectures in MFC are, the easier it is to make this determination. So take the time to experiment with MFC. Read the documentation. Learn to browse the sources and use the wizards. Study the source files. And talk to other MFC programmers, who are rapidly building up the lore of MFC programming. Soon you'll be writing successful MFC applications and making your own contributions to MFC lore. And you'll be using the C++ API for Windows.


Topic Not Found

No matching topic was found. Some possible reasons and remedies are as follows:

  • The file (.chm) that contains the destination topic is not in the expected location.

    Remedy: Rerun the MSDN Library Setup.

  • The file (.chm) that contains the current topic or destination topic is out-of-date and the link is no longer valid.

    Remedy: Install the latest version of the MSDN Library.

  • The MSDN Library CD is not in the CD-ROM drive.

    Remedy: Insert the MSDN Library CD into the CD-ROM drive.