Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Extending Visual C++


Dr. Dobb's Journal May 1997: Extending Visual C++

John, who has been developing commercial Windows applications since Windows 2.0, currently focuses on OLE COM, MAPI, Exchange, and Internet technologies. He can be contacted at [email protected].


In the beginning, GENERIC, the classic example skeletal program for Windows, formed the starting point for countless Windows programs. Subsequent programmers built their own enhanced versions of GENERIC that contained support for the features their particular applications needed. These features included toolbars and status bars, file-load and save dialogs, database access code, or messaging-system API calls.

Then came MFC, with standardized support for these (and more) features. The framework also included a way to generate a GENERIC called "AppWizard," enabling developers to walk through a series of dialog steps to create the skeleton application. Today's skeletons are more than bare bones; they have become quite muscular, in fact. The current version of Visual C++ supports several types of applications:

  • MFC executable, including OLE, ODBC, and MAPI support.
  • MFC DLL.
  • OLE ControlWizard.
  • Windows application.
  • Windows DLL.
  • Console application.
  • Static library.
  • Makefile.
  • ISAPI Extension Wizard.
  • Custom AppWizard.

Each of these application types has a corresponding AppWizard to create an instance of the type. Some are built into the core AppWizard, and some are implemented by DLLs that extend the AppWizard. A custom AppWizard, the focus of this article, is an extension of the core AppWizard technology built into Visual C++. It is a GENERIC generator that lets you add GENERIC generators to the list. If you find yourself needing a modern version of an old enhanced GENERIC, this is the place to build it. A custom AppWizard project creates a skeleton of a custom AppWizard. The custom AppWizard you create from the skeleton generates an instance of your special GENERIC.

In this article, I'll show how to create a custom AppWizard that makes extension DLLs to the Microsoft Exchange e-mail client (also known as the "Windows 95 Inbox").

Exchange Client Extensions

The Windows 95 Inbox user interface can be extended by writing Exchange Client Extensions that extend or replace existing commands, add new commands, and hook a variety of events to allow custom processing of messages and commands. An Exchange Client extension is a DLL that exports OLE Component Object Model (COM) interfaces that support predefined extension objects. Microsoft has defined 11 extension interfaces, 9 of which you can implement. While they have some limitations, these interfaces provide a powerful means to take advantage of the Universal Inbox in Windows 95.

Writing client extensions or doing MAPI programming presumes you know something about COM. Except for Simple MAPI, all of MAPI and the extensions are implemented as OLE COM interfaces. This is the lowest level of the OLE technology hierarchy. All of Microsoft's technologies are being cast into this model.

Extensions are special cases of COM interfaces, implemented in a DLL, and registered with the Exchange Client. They are special cases because they register in the Exchange Extension area of the registry instead of as standard OLE GUIDs and Class IDs. Instead of using the standard CoCreateInstance mechanism, they export a special entry point that Exchange calls to create object instances. Other than these exceptions, they are vanilla component objects. They are easily implemented in C++.

How a Custom AppWizard Works

The code of a custom AppWizard contains at least three classes:

  • One derived from CCustomAppWiz.
  • CDialogChooser.
  • One or more derived from CAppWizStepDlg.

A separate class is created for each of the custom steps (called CCustomNDlg), where N is the one-based number of the step. In this example, there is a single custom step.

The class derived from CCustomAppWiz is CExchXAppWiz; it handles initialization, step navigation events, and termination. CDialogChooser handles the creation of the list of steps and the lives of the dialogs in it. CCustom1Dlg handles the user entries and communication of the dialog contents to AppWizard.

As each dialog is exited, it tells AppWizard the current user selections by adding macros to the AppWizard dictionary. When the Finish button is pressed, AppWizard populates a confirmation dialog based on the user's selections using the contents of the file CONFIRM.INF.

When confirmed, AppWizard processes the application template files based on the contents of the dictionary and the instructions in the file NEWPROJ.INF to create the new application instance.

How to Build a Custom AppWizard

Given all the pieces that are involved in custom AppWizard work, you can get confused. As Figure 1 illustrates, these pieces include the project files of the project to be cloned, the custom AppWizard project, custom resource templates, the core AppWizard, and the cloned project instance. The steps required to build a custom AppWizard (see Figure 2) are:

1. Get the project you want to clone working cleanly. Once you start the process of making a clone, the source files of the project you are cloning will have macros and directives sprinkled through them. You don't want to be forced to manually propagate fixes from the original source files.

2. Decide the starting point for your custom AppWizard. You can base your custom AppWizard on an existing MFC project, standard AppWizard steps, or your own custom steps. If you base it on an existing project, you cannot add any custom steps, but AppWizard can help with the creation of templates. The project type will be the same as the project on which the custom AppWizard is based. If you base it on the standard AppWizard steps, you can add custom steps, and the project type will be selectable by users between an MFC EXE and MFC DLL. Templates are built by hand editing. If you select custom steps, wire it up by hand; the project type is always MFC EXE. The Exchange Extension Wizard needed custom steps and did not want to be an MFC DLL, which doesn't fit into any of the choices. This kind of thing seems to happen to me regularly. There is a workaround, which I will show you later.

3. Create a custom AppWizard project. To create a custom AppWizard, use the Custom AppWizard Project type in the New Project Workspace dialog. Select the starting point and finish the skeleton project generation.

4. Edit the dialog resources to customize steps. If you selected custom steps or standard AppWizard with custom steps, use the resource editor to edit the dialogs to add the controls for your custom steps. There is a dialog template and class for each of the custom steps. Then use ClassWizard to add a member variable for each control on each dialog. ClassWizard will generate the code to use Dynamic Data Exchange (DDX) to set these variables to the values of the dialog controls.

5. Edit the OnDismiss member function. This is where your custom AppWizard captures the user's selections and tells AppWizard about them. Each of the step dialog classes AppWizard generated for you has an OnDismiss member function. To notify AppWizard of user choices, set a key you define to a value corresponding to what the user selected. These values are usually Booleans denoting whether or not the user selected a given feature. Use the key string as the macro when editing the templates to conditionally include code features.

Set the dictionary values using CMapStringToString calls on the m_Dictionary member of the CCustomAppWiz-derived class. This is done for each step you define in the custom AppWizard. Listing One sets the dictionary variable based on the user's selection of a command interface.

6. Create the custom resource templates. The custom resource templates are a special form of the application you want to clone, modified to allow AppWizard to change filenames and class names.

The custom resource templates are built from the working application you want to clone. The custom resource templates come in two types: binary and text. Binary templates are files of binary resources; for example, BMP files, which you tell AppWizard to copy to final project files by statements in NEWPROJ.INF. They need not be binary -- the real distinction is whether or not the resource will be run through the AppWizard parser as part of the process of creating the application instance. Text templates are sent through the AppWizard parser when the application is created. They contain macros and directives that AppWizard uses to make instance-specific name substitutions and conditional feature-inclusion decisions. Text templates are typically source files such as .H, .CPP, .RC, and .ODL files, although there is no restriction on the extension.

If you are creating the custom AppWizard from an existing project, the Custom AppWizard Wizard can help you create the templates, providing you didn't make any changes AppWizard can't handle. Note that there are restrictions:

  • Characters in the project name must be alphanumeric (including DBCS characters).
  • The application base class name must be unaltered.
  • The filenames must be unchanged.
  • Classes added to the project with ClassWizard will not have macros when converted to template form. This means their names will not be parsed into the project name entered by the AppWizard user when creating a new project. (Macros have to be added manually to these text templates.)

The custom resource templates are stored in the Template subdirectory of the project directory. If you didn't start from an existing project, or AppWizard didn't really finish the job, you have to manually create the template files.

Copy the minimal files comprising your clone project to your custom AppWizard project's Template subdirectory. The minimal files are typically C, CPP, H, RC, DEF, BMP, and MAK. I left out the MDP file because it has embedded path names and filenames that can't be processed through macros. Visual C++ can recreate the MDP file from the makefile.

Edit the files to replace filenames and class names you want to make project specific with macros.

Edit the files to add AppWizard directives to control which code gets included in a generated project instance.

Macros are bracketed in double dollar signs, except when within a directive. In Listing Two, $$root$$ expands to the project name. $$IF() and $$ENDIF bracket the if directive. In the statement $$IF(COMMANDS), if the COMMANDS macro evaluates to true, the code up to the $$ENDIF is written to the output stream. Assume COMMANDS is True and the other macros are False and the project name is ExchExt. The parser would emit the code in Listing Three.

Add the template files to the AppWizard project as custom resources by inserting them as resources of type "TEMPLATE." Use the Insert Resource dialog, select "TEMPLATE" and press the Import button. Set the "Files of Type" drop-down menu to "All files," and the "Open As" drop-down to "Custom." Select the file containing the custom resource and press the Import button to bring it into the resources. When the Custom Resource Type dialog appears, select "TEMPLATE" as the type. Change the name of the newly imported resource from IDR_TEMPLATE*to the filename, or whatever name you intend to put in NEWPROJ.INF for it. You need to put the name in quotes to prevent the Developer Studio from complaining about invalid characters in the name. Once imported in this way, the changes to the underlying files will be reflected in the resources after the next build. The Developer Studio will notice they have been changed and prompt you to reload them. You can now just edit the Template files and rebuild.

7. Edit CONFIRM.INF, which contains the descriptive text for the options supported by your custom AppWizard. Use directives to include only those the user selected in the step dialogs.

8. Edit NEWPROJ.INF, which will contain any commands you add that control the final project creation. Add commands to create project subdirectories, to copy binary resource templates, and to parse text-resource templates. The templates will be extracted from the custom AppWizard project resources into the final project files. Each line contains one command. There are two basic forms of commands. The first form, beginning with the slash character, just creates a project subdirectory. The second, a tab-delimited source and target, controls how a custom resource template is turned into a final project file; see Table 1.

Make sure you delimit the source and targets on a line with the tab character. This will prevent an "Illegal item in newproj.inf" message when AppWizard begins processing NEWPROJ.INF.

9. Build the custom AppWizard. The custom AppWizard project creates a DLL, with the extension AWX.

After the link, the build copies the custom AppWizard to the Visual C++ shared template directory, typically C:\MSDEV\Templates. From this point, the custom AppWizard is available in the New Project Workspace dialog in the Type list box.

You can change the icon that appears next to the type name in the listbox by changing the icon in your custom AppWizard project resources. Be sure to read the README.TXT file that AppWizard creates for your custom AppWizard project for description of the files created.

10. Debug the custom AppWizard. The custom AppWizard is a DLL, so it requires an executable for the debug session. The executable is the Developer Studio itself, MSDEV.EXE. You debug using two instances of the Developer Studio. Set the breakpoints in the first instance, the one which has the custom AppWizard project open. Use the second as the executable for the debug session. In the second instance, select your custom AppWizard from the New Project Workspace dialog.

To debug the Exchange extension created by the Exchange Extension Wizard, use the Exchange client, EXCHNG32.EXE, as the executable for the debug session.

The Custom AppWizard

The Exchange Extension Wizard (EEW), the custom AppWizard I present here, generates a skeleton Exchange extension project. You select the interfaces you want from the available selections. I included the interfaces I use the most, IExchExt::Commands, IExchExt::MessageEvents, and ExchExt::UserEvents. These interfaces, along with related source code and binary files, are available electronically; see "Availability," page 3.

Select the Exchange contexts you want the extension to participate in. Extension Contexts are states of operation the Inbox manages in order to make extensions work more efficiently. Extensions can selectively register in these states. The extension object's lifetime is then tied to the state, and the number of extensions active simultaneously can be minimized by good extension design.

Many of the states are associated with windows on the screen. For example, the Send Note Message context corresponds to the appearance of the Compose Note window on screen. The Remote Viewer context is alive when the Remote Mail window is on screen. The Viewer context lives while the main Exchange window is running.

EEW lets you select from two of the available contexts. You can also use the other contexts; they are easy but tedious to add. Add check boxes for their selection in the ExchWz project, OnDismiss code to put them in the dictionary, and directives in the ExchX.CPP resource to include the code in IExchExt::Install.

There are calls to perform miscellaneous tasks in many of the interface member functions I've included: Code to get the current user selection and message object, initialize custom menu options, add buttons to the customize toolbar dialog, detect user initiation of custom commands, handle context-sensitive help, and so on.

EEW installs a submenu on the Tools menu, then adds two commands to it with associated toolbar and help connections. It adds a help menu item after the Microsoft Exchange Help Topics menu item. It also shows how to use the registry to store the help filename and path. No help file is supplied, so you'll need to create with appropriate topics to actually see the help work.

The Custom AppWizard Wizard creates starter files for providing context-sensitive help in your custom AppWizard. These are not used in EEW.

OutputDebugString calls are at the beginning of each member function so you can see the call sequences in the debug window when debugging your extension. Use the Exchange client, EXCHNG32.EXE, as the executable for the debug session for the project you build with the EEW.

To create an Exchange Extension project, select the Exchange Extension Wizard project type in the New Project Workspace Dialog and give it a name. When you press the Create button, the single custom step is displayed; see Figure 3. Select the interfaces and contexts desired and press the Finish button.

After building the new project files for the extension, the EEW asks you to open a makefile. This is a workaround to the project-type problem mentioned earlier.

The Exchange client needs a registry entry to find your extension DLL. Use the registry editor to put an entry for your extension in HKEY_LOCAL_MACHINE/ SOFTWARE/Microsoft/Exchange/Client/Extensions. You can name it whatever you want; it's the string value that Exchange cares about.

The format for the registry entry for the sample extension is:

Your Extensions "4.0;<insert your drive and path here><projectname>32.dll;1;<context bitmap>;<interface bitmap>";

for example,

My Exchange Extensions "4.0;c:\mydir\myext32.dll;1;010001;1101".

In order for an interface in your extension to participate in a context, the following criteria must be met:

  • The registry entry for the extension must have the context-map bit position set for the context desired.
  • The interface-map bit position for the interface must be set.
  • Your extension's implementation of IExchExt::Install must return S_OK when called by Exchange for the context. This tells Exchange you want to play in the context. The second argument passed in to IExchExt::Install is the context ID. Symbolic constants for the contexts are defined in EXCHEXT.H and start with EECONTEXT_. When you check out the registry entries, you will discover that Exchange uses the extension mechanism itself; see Figure 4.

Problems Encountered

When testing the EEW, I ran into a limitation in custom AppWizards. The custom AppWizard does not allow you to control the makefile generation process. It always creates a makefile that it thinks is appropriate. In the case of an AppWizard using custom steps, it thinks an MFC EXE is appropriate. I wanted to create a C++ non-MFC DLL, simply by making a text custom resource template for the makefile. When AppWizard processed the project, it overwrote my processed makefile with its own.

Microsoft engineers confirmed the limitation and told me it was by design. There are APIs to manipulate project settings, but these APIs are only disclosed to ISVs who have active projects for Visual C++ add-ons. If you create a custom AppWizard from an existing AppWizard project, it will track the original project settings, but you cannot add custom steps. If you choose the option to start from Standard AppWizard steps, you can add custom steps, but you only have the choices of an MFC EXE and an MFC DLL, and whether or not to use MFC in a shared library. This seems like a serious limitation, especially since the whole idea of having such a thing as a custom AppWizard is to extend the Developer Studio. Some things, like Exchange Extensions, don't really benefit from using MFC.

I worked around this problem by giving my custom resource template for the makefile a name that doesn't collide with what AppWizard wants to do. It takes the form "project32.MAK." After the EEW creates the new project, open this makefile manually: Go to File Open and select project32.MAK, then select Makefile in the Open As drop-down menu. When you hit the Open button, the workspace will be set up properly and the project will build correctly.

The EEW puts up a message box to remind you to do this and also deletes the offending makefile that AppWizard insists on creating. Microsoft seemed receptive to my suggestion that a custom AppWizard be at least allowed to suppress the creation of the makefile. This situation provides an example of how to use ExitCustomAppWiz as a convenient place to put processing immediately before the custom AppWizard is unloaded. Unfortunately, there is no documented way to detect in ExitCustomAppWiz that users have pressed Finish instead of Cancel, so the dialog still pops up when you cancel. The code also demonstrates how to access the dictionary macro values from within the custom AppWizard.

Speaking of problems, while I was on the phone with Microsoft, Version 4.2 of Visual C++ arrived. When I asked if there were any improvements in the custom AppWizard support, Microsoft said more project types are supported, but there is also a new bug. Custom AppWizards built with Version 4.2 will not run with Version 4.2. They will run on Version 4.1. Apparently, a DLL in the package was built with prior version code. Microsoft referred me to Knowledge Base article Q152484 at http://www.microsoft.com/visualc/.

DDJ

Listing One

BOOL CCustom1Dlg::OnDismiss(){
   if (!UpdateData(TRUE)) // do DDX
      return FALSE;
   // Set template variables based on the dialog's data. 
   // m_bCommands is the dialog class member variable
   if(m_bCommands)
      ExchWzaw.m_Dictionary["COMMANDS"] = "Yes";
   else
      ExchWzaw.m_Dictionary.RemoveKey("COMMANDS");
   return TRUE;  // return FALSE if the dialog shouldn't be dismissed
}

Back to Article

Listing Two

$$root$$::$$root$$ () 
{
    OutputDebugString("$$root$$::$$root$$\n");
       
    m_cRef = 1;
    m_context = 0;
    $$IF(COMMANDS)
       m_pExchExtCommands      = new $$root$$Commands(this);
$$ENDIF$$IF(USEREVENTS)
   m_pExchExtUserEvents    = new $$root$$UserEvents(this);
$$ENDIF$$IF(MESSAGEEVENTS)
   m_pExchExtMessageEvents = new $$root$$MessageEvents(this);
   $$ENDIF
}

Back to Article

Listing Three

ExchExt::ExchExt ()
{
     OutputDebugString("ExchExt::ExchExt\n");
        m_cRef = 1;
        m_context = 0;
        m_pExchExtCommands      = new ExchExtCommands(this);
}

Back to Article


Copyright © 1997, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.