| Some of the work I do for clients requires developing several similar applications repetitively. When I invoke File | New, Visual C++Â® has a whole list of AppWizards ranging from ATL COM servers to MFC applications to plain vanilla Win32Â®-based apps. I quickly tire of rewriting these applications over and over, so I revert to the old Edit | Copy | Plagiarize routine. Surely, there must be a way to get my own little AppWizard into that list. Steve Stimpson|
via the Internet Yes, there is a way to get your own AppWizard in that listâ"but you have to write one first. This month I'll discuss the Visual C++ AppWizards. I'll cover the architecture of the AppWizards and show you what it takes to create one.
Back in the days of WindowsÂ® 2.0 (1989 or so) I would have loved an application generator. In those days, most developers started a Windows project armed only with a copy of Charles Petzold's Programming Windows and the Windows SDK. Even the Windows SDK documentation recommended the editor inheritance method of application development.
Of course, as a general rule you should understand the fundamental underpinnings of any technology you are working with. However, there comes a point where writing the same code again and again becomes a drill, not to mention a waste of time. To address this issue, the Visual C++ environment comes replete with a list of code generators that will start you up with all types of projects. The available projects appear under the Projects tab when you select File | New. Just select the type of project you want, run through the dialogs to configure the project, and press the Finish button. Voilï¿½â"you've got a working EXE or DLL.
AppWizard Architecture The idea behind writing a custom AppWizard is that once you get a certain type of project debugged and working, you should be able to take that project and easily regenerate the source codeâ"albeit with a few appropriate substitutions here and there. This is the premise upon which the Visual C++ AppWizards are built.
When the Visual C++ New Projects dialog comes up, Visual C++ scans for all the files with an AWX extension to determine the projects available through the New Projects dialog box (AppWizard DLLs have an AWX extension). Figure 1 shows the New Projects AppWizard dialog box.
|Figure 1 Many AppWizards to Choose From |
Visual C++ comes with an AppWizard engine that knows how to talk to the various Visual C++ AppWizard DLLs that are installed. The AppWizard DLLs contain code for managing the options dialog boxes and code generation. As you highlight each AppWizard listed in the dialog box, Visual C++ loads that DLL and performs some initialization. (I'll cover this in a moment.) The AppWizard presents several dialog boxes offering options for configuring the code being generated. The actual source code for the application being generated is held as text resources within the DLLs.
The source code in the templates is marked for substitutions and conditional code inclusion segments using syntax understood by the AppWizard engine. For example, you probably want to be able to create your own names for the classes and files in your project, or you might want to include (or exclude) certain bits of code, depending on the options that were selected.
After you press the Finish button, the AppWizard culls through a list of files contained as resources in the DLL, searching for appropriate substitution and inclusion conditionals from a lookup table that was set up through the options dialog boxes. The AppWizard parses the source files to create the application source files, and then creates a project file. Then you can open the project file in Visual C++ and build the project.
There are several components to the AppWizard architecture, including the AppWizard engine DLL, the source code template, the options dialog classes, the dialog chooser class, and the custom AppWizard class (which holds a symbol lookup dictionary for performing substitutions and conditional code inclusion). Figure 2 shows a high-level view of the AppWizard architecture. Let's take a closer look at what's happening.
|Figure 2 AppWizard Architecture |
The centerpiece of the Visual C++ AppWizard technology is a DLL named MFCAPWZ.DLL, which contains the code for controlling the default behavior of an AppWizard DLL. MFCAPWZ.DLL also controls the interactions between Visual C++ and the AppWizard DLLs.
The Visual C++ AppWizards are interesting because they are DLLs that don't have any entry points. When an AppWizard is selected in the New Projects dialog box, its first job is to pass a pointer of type CCustomAppWiz to MFCAPWZ. CCustomAppWiz is a C++ class that defines the entry points into the AppWizard DLL. MFCAPWZ simply drives the code generation through the MFCAPWZ DLL. Let's take a look at CCustomAppWiz.
CCustomAppWiz The class CCustomAppWiz lies at the heart of each AppWizard. That is, each AppWizard has to contain an instance of some class ultimately derived from CCustomAppWiz because CCustomAppWiz serves as the communication link between the AppWizard engine and the AppWizard DLL. You'll find the class defined in Program Files\Microsoft Visual Studio\VC98\include\customaw.h. Figure 3 shows the definition of the CCustomAppWiz class. I'll discuss this class in more detail a bit later.
The first order of business for an AppWizard to perform upon loading is to pass a pointer of type CCustomAppWiz to the AppWizard engine. This usually happens during the call to DllMain when loading the DLL. The API function exposed by the AppWizard engine for doing this is named SetCustomAppWizClass. This gives the AppWizard engine a way to call back to the AppWizard. Here's the prototype for SetCustomAppWizClass:
Once the AppWizard engine and the AppWizard DLL are connected in this fashion, the AppWizard engine drives the code generation process while the AppWizard's CCustomAppWiz class and the AppWizard step dialogs guide the process.
void SetCustomAppWizClass( CCustomAppWiz pAW );
AppWizard Step Dialogs An AppWizard can have any number of steps, with each step being defined by a separate property page-like dialog box. The base class for these dialog boxes is CAppWizStepDlg:
While CAppWizStepDlg has three virtual functions, OnDismiss is really the only one you should be interested in. The AppWizard machinery calls OnDismiss every time the dialog box is dismissed. For example, this happens as a response to pressing the Back, Next, and Finish buttons. OnDismiss is where you process the options selected during that step and set up the substitutions dictionary accordingly (this is the dictionary through which the AppWizard engine will perform lookups when substituting items such as class and file names). The AppWizard engine also uses the dictionary to decide which parts of the source code to include in the application generation process.
class CAppWizStepDlg : public CDialog
virtual BOOL OnDismiss();
virtual BOOL PreTranslateMessage(MSG* pMsg);
virtual BOOL Create(UINT nIDTemplate,
CWnd* pParentWnd = NULL);
The Chooser Class So how are the CCustomAppWiz-derived class and the step dialog boxes related? Within the AppWizard, the link between the CCustomAppWiz-derived class and the AppWizard step dialogs is the dialog chooser class. The AppWizard's CCustomAppWiz-derived class holds an instance of the dialog chooser class. Figure 4 shows the AppWizard's CDialogChooser class.
DialogChooser is a simple class whose mission is to manage the AppWizard step dialog boxes. Choosing the options for an application may involve multiple steps and can follow several different paths. For instance, if you're creating a dialog-based application in MFC, it doesn't make sense to include the steps for producing an MDI-type application. So that selection process may follow a different path or track and will probably require you to show different dialog boxes. To that end, the chooser class has an array of CAppWizStepDlg objects and indexes to monitor which track the AppWizard is in and the current step within a certain track.
The DialogChooser class handles the callbacks that the AppWizard engine makes into the specific AppWizard. That is, the DialogChooser tells the AppWizard engine which dialog box to show next, depending upon the current state of the AppWizard. The AppWizard engine responds to the Next and Back buttons by calling into the AppWizard's CCustomAppWiz-derived class, which simply delegates to the DialogChooser class, as shown in Figure 5.
So that's how the application steps and the AppWizard work together. The next thing to look at is how the final code is generated. I'll start with how the AppWizard stores code as template resources.
Template Files as Resources All the source code for the project to be generated is contained as text resources in the custom AppWizard. To include a text file as a resource, just list the name you want for the file, the keyword TEMPLATE, and the path to file on disk. When the resource compiler sees this statement, it includes the file as a text resource. Figure 6 shows some sample resource instructions for doing this.
Next, the AppWizard engine needs a directory of these files. The AppWizard engine expects to see a text resource named NEWPROJ.INF, which holds the list of files to include in the project. Here's an example of what the source code for the directory file looks like:
The name of the text resource is listed on the left, and the name of the source code file as it should appear in the generated project appears on the right. This list is interesting because it shows how substitutions are made as the AppWizard generates the source code. Notice the $$ symbols surrounding the word "root". When the AppWizard engine processes the template, the AppWizard engine replaces the symbol "root" with the name of the project typed in when the New Projects dialog was originally invoked. For example, if you named your project foo, it would include the files foo.clw, foo.cpp, foo.h, foo.rc, fooDlg.h, and fooDlg.cpp.
As the AppWizard engine processes each file, it scans the file for conditional inclusion and substitutions. For example, in the project being generated, imagine you wanted to make the About box optional. You would simply surround any source code referring to the About box with a conditional inclusion, as shown in Figure 7. Figure 7 also shows how the name of a project is inserted within the dialog box class name.
Finally, the AppWizard engine needs to know what to display within the final confirmation box. A well-known file named CONFIRM.INF usually contains a human- readable description of the project components and the features selected.
Now let's take a look at how the substitutions and conditional code inclusion symbols are set up.
Adding and Removing Dictionary Items The application is finally generated in response to pressing the Finish button. At that point, the AppWizard engine pulls out a list of files from the custom AppWizard's resources and begins to make the substitutions and conditional code inclusions. Of course, that begs the next question: how does the AppWizard engine know what substitutions to make? The answer is that the CCustomAppWiz's dictionary holds those substitutions.
The dictionary is simply an instance of the MFC class CMapStringToString that holds key/value pairs. Each code inclusion conditional and each symbol substitute is entered into the dictionary, usually during the AppWizard dialog's OnDismiss method. OnDismiss adds or removes dictionary key/value pairs based on the options selected in the Options dialog box.
Figure 8 shows an example of handling OnDismiss to select or deselect a project's About box option.
Adding Compiler and Link Options One of the final steps in creating an AppWizard is customizing the build process. For example, the default project comes with the Run Time Type Information (RTTI) turned off (the option that lets you perform dynamic_casts). If your project has to perform dynamic_casts, then you need to have the RTTI option turned on for the projects generated by this AppWizard.
There's a hook in the AppWizard architecture for adding compiler and linker options. Your CCustomAppWiz-derived class has a function named CustomizeProject specifically for tacking these options onto the end of your project file. CustomizeProject is called after all the files have been generated. The AppWizard architecture passes an interface named IBuildProject (see Figure 9) into your AppWizard's CCustomAppWiz-derived implementation of CustomizeProject. Overriding CustomizeProject involves using the object model represented by IBuildProject to add options to the project's build process. To see how to add the RTTI option switch to the compile project, see Figure 10.
The Custom AppWizard AppWizard Writing AppWizards by themselves is actually a mundane job. So Visual C++ includes an AppWizard for creating AppWizards. To quickly create a new AppWizard, select Custom AppWizard from the File | New Projects dialog box. The Custom AppWizard AppWizard lets you create one of three types of AppWizards.
You can create an AppWizard based upon an existing project. With this option, you simply point to a preexisting project and ask the Custom AppWizard to generate an AppWizard that creates clones of the existing project. This is usually the easiest option to work with whenever you've got the original program working and debugged all the way. From there, developing your own AppWizard is a matter of finding the optional code and surrounding it with AppWizard conditionals.
You may also replicate the standard MFC AppWizard that features the normal MFC code generation steps (you knowâ"MDI versus SDI versus Dialog apps, whether or not to use Automation, specifying the class and file names, and so on). From there, you may also introduce custom steps. The Custom AppWizard asks you how many extra custom steps you want and adds an Options property page for each property page you want to include. In addition, this option lets you generate a localized AppWizard; you can pick the locality you want to use (such as English, Spanish, French, and so on). This option is useful whenever you want to add more features to the standard MFC applications. For example, imagine you have various compelling features available through some DLLs you've written or through some third-party tools. You might want to write an AppWizard that generates regular MFC applications, but inserts the compelling features based on selections made through the custom AppWizard.
Finally, you may create a completely custom AppWizard with a predetermined number of steps. The AppWizard you get out of this process doesn't do anything except show a couple of blank property pages and produce a blank project file. From there it's completely up to you to instruct the AppWizard regarding which files to include in the project. Of course, this option affords the most flexibilityâ"though it means more typing.
Conclusion Whenever you need to generate a certain type of application over and over again, it makes great sense to use a code generator. Many projects involve a certain amount of boilerplate code that doesn't really change from one project to another. For example, many MFC-based applications have similar infrastructure requirementsâ"and that's why there is an MFC AppWizard. If you're in a position where you need to generate similar boilerplate code repeatedly, the Visual C++ AppWizard is one of the best ways to manage the process.