Chapter 12

Creating Windows Messaging Client Extensions


CONTENTS


Up to this point, you've learned how to create standalone MAPI applications and how to use the Exchange Forms Designer to create MAPI-enabled forms that run under the Windows Messaging client interface. In this chapter, you'll learn how to use C++ to create direct extensions to the Microsoft Exchange interface. You'll see how easy it is to add new property pages to the Microsoft Exchange menus. You'll also see how you can build programs that execute at selected times throughout the life of a Microsoft Exchange session.

In the first part of the chapter, you'll get a review of the Microsoft Exchange extension interface. You'll learn about the various extension types and how you can use context mapping to determine when your client extension is called by Microsoft Exchange.

In the second part of the chapter, you'll use C++ to create a program that adds a property sheet and several message event extensions to the Windows Messaging client. This program will act as a message checksum verifier. You'll be able to turn this new message extension on and off by selecting Tools | Options from the main Microsoft Exchange menu to find your extension property page.

When you're finished with this chapter, you'll understand the theory behind Windows Messaging client extensions and have experience creating your own working extension.

Note
The project in this chapter was built using Microsoft Visual C++ 4.1. You may need to make slight changes to the code if you plan to compile it using another version of C++. If you do not have a C++ compiler, you can still get a lot out of this chapter. The complete source code and a compiled version of the project can be found on the CD-ROM. You can review the source code as you read the chapter and install the compiled extension as instructed.

What Are Exchange Client Extensions?

Before we jump into the task of creating a Windows Messaging client extension, it's worthwhile spending some time reviewing the theory behind Windows Messaging client extensions and how they can be used to create programs that work within the Windows Messaging client environment.

This section of the chapter covers

In the next section, you'll build a working Windows Messaging client extension using VC++.

How Microsoft Exchange Client Extensions Work

The heart of the Windows Messaging client extension model is to expose all major process events of the Windows Messaging client to make them available for other programs to monitor and, when appropriate, modify or replace standard Windows Messaging client actions. For example, you can write an extension that scans the incoming message for selected words and then immediately moves that message to another folder or even forwards the message directly to another user. You can write extensions that automatically add additional text at the bottom of the message body when the user saves the message or submits it for delivery. You can even write extension code that executes when a user attempts to resolve recipient names to the installed address books. You can also write an extension that will be executed when a user accesses an attachment (for example, to check for viruses).

There are additional extension sets that allow you to add property pages to the Microsoft Exchange main menu (Property extensions); extension sets to add entirely new menu commands to Microsoft Exchange (Command extensions); new features for message searching (Advanced Criteria extensions) and others.

To accomplish all this, Microsoft has created a large set of API calls and callback functions that can be used to create Dynamic Link Libraries (DLLs) that intercept Windows Messaging client messages and then, when appropriate, jump in and perform any desired task before returning control to the Windows Messaging client. In effect, your extension program becomes part of the Windows Messaging client (see Figure 12.1).

Figure 12.1 : Windows Messaging client extensions become part of the Windows Messaging client.

Microsoft Exchange is designed to allow several extension DLLs to be loaded at one time. In fact, many of the Windows Messaging client features that appear on the menu are simple extension DLLs designed and shipped by Microsoft. For example, if you have installed the Internet Mail features of Microsoft Exchange, you have really installed an extension DLL. There are several Microsoft Exchange extension DLLs currently available, and many more will be available in the future.

Advantages of Microsoft Exchange Client Extensions

There are some real advantages to creating Windows Messaging client extensions instead of creating standalone MAPI applications. First, if you want to add just one or two features to the client interface, it's a lot easier to code those few additional features instead of doing the work to code a complete MAPI client to replace the Windows Messaging client. Second, by using client extensions, you can customize the Microsoft Exchange interface to meet your needs without losing the power of Microsoft Exchange.

An added benefit is that not only can you install your own special Microsoft Exchange features, you can also take advantage of other extension DLLs by other vendors. And, because the interface is well defined and open to all programmers, it is not too likely that you'll design an extension that is incompatible with other DLLs you'll install later.

Finally, by using the DLL extension to add features to Microsoft Exchange, users can use a familiar interface while taking advantage of the unique add-ins you've developed. Also, Microsoft has pledged to make sure that any extensions built for Microsoft Exchange will run under all versions of the Windows Messaging client. This means you can write routines that can potentially be installed on NT, Windows 95, Windows for Workgroups, and Windows 3.1 Windows Messaging client software.

The Microsoft Exchange Client Contexts

In order to allow DLLs to monitor Windows Messaging client processes, a set of context messages has been developed to inform any DLLs of the current Windows Messaging client process in progress. These context messages make up what is called the Context Map. This map lists all the general process events that can be monitored by an installed extension DLL. Table 12.1 shows the list of contexts along with a short description.

Table 12.1. The Windows Messaging client extension context map.
Context Map Name Description
TASK This context covers the entire Microsoft Exchange program session, from program start to program exit. Note that this may cover more than one logon, since users can log off a session without exiting the Windows Messaging client.
SESSION This context spans a single MAPI session, from the logon to logoff. As noted above, multiple logons can occur during a single execution of Microsoft Exchange. These sessions may or may not overlap (that is, the user could log into MAPI services from another instance of Microsoft Exchange).
VIEWER This context covers the time when the main viewer window has focus. You can use this to add features to the process of selecting messages or folders.
REMOTEVIEWER This context covers the time when the Remote Mail window is displayed when the user chooses the Remote Mail command.
SEARchVIEWER This context covers the Find window that is displayed when the user chooses the Find command.
ADDRBOOK The context for the Address Book window that is displayed when the user chooses the Address Book command.
SENDNOTEMESSAGE The context for the standard Compose Note window in which messages of class IPM.Note are composed.
READNOTEMESSAGE The context for the standard read note window in which messages of class IPM.Note are read after they are received.
READREPORTMESSAGE The context for the read report message window in which report messages (Read, Delivery, Non-Read, Non-Delivery) are read after they are received.
SENDRESENDMESSAGE The context for the resend message window that is displayed when the user chooses the Send Again command on the non-delivery report.
SENDPOSTMESSAGE The context for the standard posting window in which existing posting messages are composed.
READPOSTMESSAGE The context for the standard posting window in which existing posting messages are read.
PROPERTYSHEETS A property sheet window.
ADVAncEDCRITERIA The context for the dialog box in which the user specifies advanced search criteria.

As you can see from Table 12.1, there are quite a few different context messages that you can use to monitor Windows Messaging client processing. Also, more than one of these contexts can be active at the same time. For example, the TASK context is always active when the Windows Messaging client is loaded. At the same time, as soon as the user logs onto a MAPI session, the SESSION context is active, too. As users perform finds, read messages, post notes, and so on, each of the contexts becomes active. And as each process ends, the context becomes inactive. This can be a bit confusing at first glance. However, you needn't worry about knowing what contexts are active at any given moment. You'll write your extension to become active when a certain context becomes active. When that happens, your code executes.

The Microsoft Exchange COM Interface

Along with the Microsoft Exchange contexts, Microsoft has developed a set of Component Object Model (COM) interfaces to react to the various events associated with a Windows Messaging client process. Most of the Microsoft Exchange COM interfaces relate directly to a Microsoft Exchange context. Table 12.2 shows a list of the Microsoft Exchange COM interfaces and gives short descriptions of them.

Table 12.2 The Windows Messaging client COM interfaces.
Microsoft Exchange COM Interface Description
IExchExt:Iunknown Can be used to load extension objects in all contexts. Most extension objects are designed to operate only within a particular context or set of contexts, but some can operate in all contexts. Use this primarily in conjunction with the IExchExtCallBack interface.
IExchExtAdvancedCriteria Used to enable extension objects to replace or enhance the functionality of the Advanced dialog box that appears when the user selects the Advanced button from the Find dialog box.
IExchExtAttachedFileEvents Used to enable extension objects to replace or enhance the default attachment-handling behavior of Microsoft Exchange.
IExchExtCallBack Enables extension objects to retrieve information about the current context. IExchExtCallBack uses the methods of the IUnknown interface for reference management.
IExchExtCommands Used by extension objects to add and execute custom menu or toolbar command buttons. Extension objects can also replace existing Microsoft Exchange commands or enhance their behavior before Microsoft Exchange carries them out.
IExchExtMessageEvents Used by extension objects to give them the ability to replace or enhance the default message-handling behavior of Microsoft Exchange.
IExchExtPropertySheets Used to enable extension objects to append pages to Microsoft Exchange property sheets.
IExchExtSessionEvent Used to enable an extension object to respond to the arrival of new messages. Use this interface to build custom inbox processing routines.
IExchExtUserEvents Used to enable an extension object to handle changes to the currently selected list box item, text, or object.

Most of the COM interfaces are self-explanatory. However, a few deserve some extra attention. The IExchExt:IUnknown interface is used mostly by the IExchExtCallBack interface. Although it is possible to build complete context interfaces using IExchExt:IUnknown, it's not very practical-especially when you have all the other COM objects to work with.

Also, the IExchExtUserEvents interface returns general information about user actions in the Windows Messaging client. Whenever the focus changes from one Microsoft Exchange object to another (that is, the selected folder or message), you can have your application execute some code.

Finally, there are two other interfaces not listed in Table 12.2. The IExchExtModeless:IUnknown and IExchExtModelessCallBack interfaces can be used to develop non-modal processes that run alongside the Windows Messaging client and intercept messages normally addressed to the client.

Note
Creating modeless add-ins is beyond the scope of this book. If you're interested in creating a modeless extension for Microsoft Exchange, you can find additional information in the Microsoft MAPI SDK documentation.

Mapping Contexts to COM Interfaces

In order to write your program to become active for the various contexts, you need to use one (or more) of the Component Object Model interfaces that match up to the Microsoft Exchange contexts. In other words, you "map" your program code to contexts using the COM interfaces. When you register your program with one or more of the COM interfaces, your program receives alerts from Microsoft Exchange that fire off methods within the COM interface. You can place code within these methods to make sure that your program performs the desired actions at the right time.

For example, if you want your program to execute whenever the SESSION context occurs, you'll need to use the IExchExtSession COM interface object in your program. Some of the contexts can be accessed using more than one COM interface. For example, The IExchExtMessage COM interface is called from several of the Microsoft Exchange contexts (SENDNOTEMESSAGE, READNOTEMESSAGE, SENDPOSTMESSAGE, READPOSTMESSAGE, READREPORTMESSAGE, and SENDRESENDMESSAGE). To make it a bit more complex, a single context can alert more than one COM interface. For example, the ADDRBOOK context notifies the IExchExtCommands, IExchExtUserEvents, and IExchExtPropertySheets interfaces.

Table 12.3 shows the Microsoft Exchange contexts along with the COM interfaces that can be used to monitor those contexts.

Table 12.3. Mapping Microsoft Exchange contexts to Microsoft Exchange COM interfaces.
Context NameInterfaces Called
TASK None. Only IexchExt::Install and IUnknown::Release are called from this context.
SESSION IExchExtSessionEvents
VIEWER, REMOTEVIEWER, IExchExtCommands, IExchExtUserEvents,
SEARchVIEWER IExchExtPropertySheets
ADDRBOOK IExchExtCommands, IExchExtUserEvents, IExchExtPropertySheets
SENDNOTEMESSAGE, IExchExtCommands, IExchExtUserEvents,
READNOTEMESSAGE, IExchExtMessageEvents,
READREPORTMESSAGE, IExchExtAttachedFileEvents,
SENDRESENDMESSAGE,
READPOSTMESSAGE,
SENDPOSTMESSAGE
IExchExtPropertySheets
PROPERTYSHEETS IExchExtPropertySheets
ADVAncEDCRITERIA IExchExtAdvancedCriteria

Now that you have a good idea of how the Windows Messaging client extensions and the Windows Messaging client contexts work together, it's time to focus on two of the COM interfaces that you'll use in the Message Signing project for this chapter:

Message Event Extensions

These message event extensions can be used to monitor the Windows Messaging client actions on incoming and outgoing messages. When your application registers the IExchExtMessageEvents interface, you can receive notifications whenever the user attempts to read, write, address, or submit a MAPI message. Table 12.4 shows the methods associated with the IExchExtMessageEvents interface along with a short description.

Table 12.4. The methods of the IExchExtMessageEvents interface.
Interface MethodDescription
OnCheckNames Used to replace or enhance the behavior of Microsoft Exchange when recipient names typed by the user are being resolved to their address book entries.
OnCheckNamesComplete Used to roll back the implementation of the OnCheckNames method in case of an error, or to release resources allocated by OnCheckNames.
OnRead Used to replace or enhance the behavior of Microsoft Exchange when reading information from a message.
OnReadComplete Used to roll back implementation of the OnRead method in case of an error or to release resources allocated by OnRead.
OnSubmit Used to replace or enhance the behavior of Microsoft Exchange when a message is being submitted.
OnSubmitComplete Used to roll back implementation of the OnSubmit method in case of an error, or to release resources allocated by OnSubmit.
OnWrite Used to replace or enhance the behavior of Microsoft Exchange when writing information to a message.
OnWriteComplete Used to roll back implementation of the OnWrite method in case of an error, or to release resources allocated by OnWrite.

You should notice that all eight of the message event methods can be sorted into four groups of two:

The first method of the pair (for example, OnWrite) is called when the user attempts the suggested operation within the Windows Messaging client. The second method of the pair (for example, OnWriteComplete) is executed immediately after the first method. This second event can be used to check results from code execution under the first method, and to perform any error recovery or cleanup that might be needed.

For example, if you decide to add an extension that encrypts messages upon submission for delivery, you could write code in the OnSubmit method of the Message Event interface that would copy the message body to a temporary file and convert it into its encrypted form. You might then write code in the OnSubmitComplete method that would check the results of the encryption to make sure all went well. If not, you could then inform the user of the problem and restore the original text. If all went well, the OnSubmitComplete method could copy the encrypted message over the original and then erase the temporary file.

In the example you'll build later in this chapter, you'll use the OnWrite and OnRead method pairs to create and check message checksums before saving and reading MAPI messages.

Property Extensions

The Windows Messaging client also has an extension for managing the creation of custom property pages. You can create a simple dialog box and then register it as one of the tabbed property pages of a Windows Messaging client object. The Windows Messaging client displays property pages for four different objects. Table 12.5 shows the four objects along with short descriptions of them.

Table 12.5. The Microsoft Exchange objects that display property pages.
Property Page ObjectDescription
Information Store PropertiesThis shows the properties of the selected MAPI Information Store object.
Folder PropertiesThis shows the properties of the selected MAPI Folder object.
Message PropertiesThis shows the properties of the selected MAPI Message object.
Tools Options MenuThis shows the properties page from the Tools | Options menu selection of the Windows Messaging client.

There are three methods to the IExchExtPropertySheets COM interface. These methods allow you to determine the number of pages that are currently registered for display, allow you to define and add a new property page, and allow you to release the property page you added to the object. Table 12.6 describes the three methods.

Table 12.6. The methods of the IExchExtPropertySheets COM interface.
Interface MethodDescription
GetMaxPageCount Returns the maximum number of pages an extension will add to the property sheet.
GetPages Adds property sheet page to the current list of pages.
FreePages Frees any resources allocated by the GetPages method.

In the project shown later in this chapter, you'll use these three methods to add a property page to the Tools | Options menu of the Windows Messaging client.

Note
The other COM interfaces mentioned here also have associated methods. These methods are not covered in this chapter. This was done to focus on the creation of a complete Windows Messaging client extension application. You can learn more about all the Microsoft Exchange COM interfaces and their methods by reviewing the MAPI SDK documentation.

Registering Extensions

Once you have successfully created a Windows Messaging client extension, you must inform the Windows Messaging client that the extension exists before it will be able to use it. This is done by making entries in the system registry database. All Windows Messaging client extensions must be registered. On 32-bit Windows systems, the client extension entries are placed in the HKEY_LOCAL_MAchINE\Software\Microsoft\Exchange\Client\Extensions section of the registry. On 16-bit systems, the registration is stored in the [Extensions] section of the EXchNG.INI file. Figure 12.2 shows the system registry editor open to the [Extensions] section.

Figure 12.2 : Viewing the Exchange/Client/Extensions section of the System Registry.

Note
In network environments, Windows Messaging client extensions can be shared among several clients. Shared Microsoft Exchange DLLs are registered in the SHARED32.INI file on 32-bit systems and in the SHARED.INI file on 16-bit systems.

The registry entry tells the Windows Messaging client several important things about the extension DLL. The syntax of an extension entry is as follows:

Tag=Version;<ExtsDir>DllName;[Ordinal];[ContextMap];[InterfaceMap];[Provider]

Table 12.7 shows each of the parts of the registration entry and explains its meaning and use.

Table 12.7. The Windows Messaging client extension registration entry.
Entry ParameterDescription
Tag An extension identifier that uniquely distinguishes the registry entry from other entries.
Version The version number of the syntax. Use 4.0 for the current release of Microsoft Exchange.
DllName The complete path and filename of the DLL containing the extension.
Ordinal An optional field that specifies the entry point into the VTable of the DLL to retrieve the extension object. If this field is empty, the default value is 1.
ContextMap An optional string made up of "0" and "1" characters that indicate the contexts in which the extension should be loaded. Any unspecified values after the end of the string are assumed to be zero. If no context map is provided, the extension is loaded in all contexts.
InterfaceMap An optional string made up of "0" and "1" characters that indicates the interfaces the extension supports.
Provider An optional string containing the PR_SERVICE_NAME of the service provider that your extension is designed to work with. For example, if your extension is designed to work with a custom address book provider, this entry would contain the PR_SERVICE_NAME of the address book provider. You should only use this parameter if your extension is provider-specific.

The ContextMap and InterfaceMap entries need some clarification. By placing "1" or "0" values in these strings, you are telling Microsoft Exchange which context events and COM interfaces you want your client to participate in. Table 12.8 shows the meaning of each position in the ContextMap parameter.

Table 12.8. The ContextMap parameter positions.
Parameter PositionContext
1
SESSION
2
VIEWER
3
REMOTEVIEWER
4
SEARchVIEWER
5
ADDRBOOK
6
SENDNOTEMESSAGE
7
READNOTEMESSAGE
8
SENDPOSTMESSAGE
9
READPOSTMESSAGE
10
READREPORTMESSAGE
11
SENDRESENDMESSAGE
12
PROPERTYSHEETS
13
ADVAncEDCRITERIA
14
TASK

For example, if your DLL extension should execute each time one of the message events occurred, you would create the following ContextMap parameter:

"00000111111000"

In addition to the ContextMap parameter, there is a parameter to inform Microsoft Exchange which COM interfaces your extension will use. Table 12.9 shows the list of COM interfaces and their positions in the InterfaceMap parameter.

Table 12.9. The COM InterfaceMap parameter positions.
Parameter PositionInterface
1
IExchExtCommands
2
IExchExtUserEvents
3
IExchExtSessionEvents
4
IExchExtMessageEvents
5
IExchExtAttachedFileEvents
6
IExchExtPropertySheets
7
IExchExtAdvancedCriteria

For example, if your DLL extension used the property sheet and message interfaces, you'd create an InterfaceMap parameter that looks like the following:

"0001010"

Later in this chapter, you'll make a registry entry to match the Message Signing Extension example described in this chapter.

Warning
It is acceptable to leave out trailing bits in the interface and context map parameters and allow Microsoft Exchange to assume these missing entries are zero ("0"). However, it is not recommended. It is much better to construct a complete string, even if most of them are set to zero.

Now that you know the theory behind creating Windows Messaging client extensions and how to register them for use, you're ready to build the sample project.

Creating the Message Signing Extension

The sample Windows Messaging client extension described in this chapter creates a checksum of each message body and stores that value before it is sent to the recipient. The same extension also checks the stored value against the computed checksum each time the user attempts to read a message. If, upon reading the message, the stored checksum is not equal to the computed checksum, it is likely that the message has been altered in some way since it was submitted for delivery by the original author. In this way, you can implement a rather simple message signature process that will give a high degree of confidence that the messages users receive have not been tampered with before they arrive at their destination.

There are four main steps to creating the Message Signing extension DLL:

Note
If you own a copy of Microsoft Visual C++, you can load the CDGEXT32 project found on the CD-ROM that ships with this book. If you do not have a copy of C++, you can still load the source code files and review them as you read along in this chapter.

Building the Initial Header File

The initial header file (CDGEXT32.H) contains basic defines, includes, and global declarations. It also contains the prototypes for the Microsoft Exchange class objects and their associated methods. Listing 12.1 shows the first part of the CDGEXT32.H file.


Listing 12.1. The first half of the CDGEXT32.H header file.
// ========================================================================
//  CDGEXT32.H
// ========================================================================
#ifndef __CDGEXT32_H__
#define __CDGEXT32_H__

//
// include files
//
#include <WINDOWS.H>
#include <COMMCTRL.H>
#include <MAPIX.H>
#include <MAPIUTIL.H>
#include <MAPIFORM.H>
#include <EXchEXT.H>

#include "RESOURCE.H"

//
// function prototypes
//
extern "C"
{
LPEXchEXT CALLBACK ExchEntryPoint(void);
}

void ErrMsgBox(HWND hWnd,  HRESULT hr,  PSTR szFunction, LPSTR szMessage);
HRESULT CheckMsgSignature(LPMESSAGE pMsg, ULONG *pulCheckSum);
BOOL CALLBACK MsgSigningDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM ÂlParam);

// global declarations
extern BOOL bSignatureOn;

//
// class declarations
//
class CDGExt;
class CDGExtPropSheets;
class CDGExtMsgEvents;

The code in Listing 12.1 first lists all the include files needed for the project (these are part of the MAPI SDK that can be found on MSDN Professional Level CD-ROMs and above). Next is the declaration of the initial entry point for the Microsoft Exchange DLL extension, along with three custom functions used in the project, along with a single variable declaration. Finally, the three COM objects that will be used in the project are declared.

Listing 12.2 shows the CDGEXT32.H code that defines the methods and properties of the high-level IExchExt interface object. This object will be used to install the extension and register the DLL for property sheet and message events.


Listing 12.2. Defining the IExchExt interface object.
//
// overall exchange extension class
//
class CDGExt : public IExchExt
{

public:
    CDGExt();
    STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef() { ++m_cRef; return m_cRef; };
    STDMETHODIMP_(ULONG) Release();
    STDMETHODIMP Install (LPEXchEXTCALLBACK pmecb, ULONG mecontext, ULONG ulFlags);

private:
    ULONG m_cRef;
    UINT  m_context;
    CDGExtPropSheets * m_pExchExtPropertySheets;
    CDGExtMsgEvents * m_pExchExtMessageEvents;

};

Next, Listing 12.3 shows the definition of the Property Sheets object.


Listing 12.3. Defining the IExchExtPropertySheets COM interface.
//
// property sheet extension class
//
class CDGExtPropSheets : public IExchExtPropertySheets
{
public:
    CDGExtPropSheets (LPUNKNOWN pParentInterface) {
    m_pExchExt = pParentInterface;
    m_cRef = 0;
    };


    STDMETHODIMP QueryInterface(REFIID   riid, LPVOID * ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef() { ++m_cRef; return m_cRef; };
    inline STDMETHODIMP_(ULONG) Release()
          { ULONG ulCount = -m_cRef;
            if (!ulCount) { delete this; }
           return ulCount;};

STDMETHODIMP_ (ULONG) GetMaxPageCount(ULONG ulFlags);
STDMETHODIMP  GetPages(LPEXchEXTCALLBACK peecb, ULONG ulFlags, ÂLPPROPSHEETPAGE ppsp, ULONG FAR * pcpsp);
    STDMETHODIMP_ (VOID) FreePages(LPPROPSHEETPAGE ppsp, ULONG ulFlags, ULONG Âcpsp);

private:
     ULONG m_cRef;
    LPUNKNOWN m_pExchExt;
};

Finally, Listing 12.4 shows the code that defines the Message Event COM interface.


Listing 12.4. Defining the IExchExtMessageEvents COM interface.
//
// message event extension class
//
class CDGExtMsgEvents : public IExchExtMessageEvents
{
 public:
    CDGExtMsgEvents (LPUNKNOWN pParentInterface) {
    m_pExchExt = pParentInterface;
    m_cRef = 0;
    m_bInSubmitState = FALSE;
    };

    STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);
    inline STDMETHODIMP_(ULONG) AddRef() { ++m_cRef; return m_cRef; };
    inline STDMETHODIMP_(ULONG) Release()
               { ULONG ulCount = -m_cRef;
                   if (!ulCount) { delete this; }
                return ulCount;};

    STDMETHODIMP OnRead(LPEXchEXTCALLBACK lpeecb);
    STDMETHODIMP OnReadComplete(LPEXchEXTCALLBACK lpeecb, ULONG ulFlags);
    STDMETHODIMP OnWrite(LPEXchEXTCALLBACK lpeecb);
    STDMETHODIMP OnWriteComplete(LPEXchEXTCALLBACK lpeecb, ULONG ulFlags);
    STDMETHODIMP OnSubmit(LPEXchEXTCALLBACK lpeecb);
    STDMETHODIMP_ (VOID)OnSubmitComplete(LPEXchEXTCALLBACK lpeecb, ULONG ulFlags);
    STDMETHODIMP OnCheckNames(LPEXchEXTCALLBACK lpeecb);
    STDMETHODIMP OnCheckNamesComplete(LPEXchEXTCALLBACK lpeecb, ULONG ulFlags);


 private:
     ULONG   m_cRef;
    HRESULT m_hrOnReadComplete;
    BOOL    m_bInSubmitState;
    LPUNKNOWN m_pExchExt;

};

This is all there is to the CDGEXT32.H header file. Next, you'll review the code for the main extension DLL.

Coding the Main DLL Routines

The CDGEXT32.CPP file contains the main body of code for the extension. The code file can be divided into five parts:

Listing 12.5 shows the initialization and main entry code for CDGEXT32.CPP. This code handles some basic housekeeping and then establishes the DLL entry point (DLLMain) and the callback address for the IExchExt object (ExchEntryPoint).


Listing 12.5. The initialization and main entry code for CDGEXT32.CPP.
// ==================================================================
// CDGEXT32.CPP
// ==================================================================

//
// module-level defines
//
#define INITGUID
#define USES_IID_IExchExt
#define USES_IID_IExchExtAdvancedCriteria
#define USES_IID_IExchExtAttachedFileEvents
#define USES_IID_IExchExtCommands
#define USES_IID_IExchExtMessageEvents
#define USES_IID_IExchExtPropertySheets
#define USES_IID_IExchExtSessionEvents
#define USES_IID_IExchExtUserEvents
#define USES_IID_IMessage
#define USES_PS_MAPI

//
// include files
//
#include "CDGEXT32.H"
#include <INITGUID.H>
#include <MAPIGUID.H>

//
// local declarations
//
MAPINAMEID NamedID[1]; // for new property
BOOL bSignatureOn = TRUE; // assume it's on
static HINSTAncE ghInstDLL = NULL;  // DLL handle


// ----------------------------------------------------------------
// Main Body of routines
//
// These two routines are the initial entry point for the DLL and
// the code that registers the extension DLL.
// ----------------------------------------------------------------

//
// to start things off as a DLL
//
BOOL WINAPI DllMain(
    HINSTAncE  hinstDLL,
    DWORD  fdwReason,
    LPVOID  lpvReserved)
{
 if (DLL_PROCESS_ATTAch == fdwReason)
 {
    ghInstDLL = hinstDLL;

 }
 return TRUE;
}

//
// register the extension
//
LPEXchEXT CALLBACK ExchEntryPoint(void)
{
     return new CDGExt;
}

Next is the code for the methods and properties of the IExchExt object. Along with the initial object constructor code, three methods must be coded:

Listing 12.6 shows the code for the IExchExt object.


Listing 12.6. Code for the IExchExt interface.
// ------------------------------------------
// Handle the creation of the CDGExt object
// ------------------------------------------
//
// These routines establish the initial
// extension interface.
//
// CDGExt::CDGExt() - constructor
// CDGExt::Release() - frees up resources
// CDGExt::QueryInterface() - main entry
// CDGExt::Install() - installs extensions
// --------------------------------------------

//
// constructor for the propertysheet and message event extension
//
CDGExt::CDGExt()
{
  m_cRef = 1;
  m_pExchExtPropertySheets = new CDGExtPropSheets(this);
  m_pExchExtMessageEvents = new CDGExtMsgEvents(this);

};


//
// frees up resources when done
//
STDMETHODIMP_(ULONG) CDGExt::Release()
{
 ULONG ulCount = -m_cRef;

 if (!ulCount)
 {
  delete this;
 }

return ulCount;

}

//
// initial interface query
// for both propertysheets and message events
//
STDMETHODIMP CDGExt::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
    HRESULT hResult = S_OK;

    *ppvObj = NULL;

    if (( IID_IUnknown == riid) || ( IID_IExchExt == riid) )
    {
        *ppvObj = (LPUNKNOWN)this;
    }
    else if (IID_IExchExtPropertySheets == riid) // for property sheets?
    {
     // ignore send/read contexts (for property sheet)
        if ( (m_context == EECONTEXT_SENDNOTEMESSAGE)   ||
             (m_context == EECONTEXT_SENDPOSTMESSAGE)   ||
             (m_context == EECONTEXT_SENDRESENDMESSAGE) ||
             (m_context == EECONTEXT_READNOTEMESSAGE)   ||
             (m_context == EECONTEXT_READPOSTMESSAGE)   ||
             (m_context == EECONTEXT_READREPORTMESSAGE) )
            return E_NOINTERFACE;

     // otherwise return the interface
        *ppvObj = (LPUNKNOWN) m_pExchExtPropertySheets;
    }
    else if (IID_IExchExtMessageEvents == riid) // for message events?
    {
        *ppvObj = (LPUNKNOWN) m_pExchExtMessageEvents;
    }
    else
        hResult = E_NOINTERFACE;

    if (NULL != *ppvObj)
        ((LPUNKNOWN)*ppvObj)->AddRef();

    return hResult;
}


//
// actually installs the extension
//
STDMETHODIMP CDGExt::Install(LPEXchEXTCALLBACK peecb, ULONG eecontext, ULONG ÂulFlags)
{
    ULONG ulBuildVersion;
HRESULT hr;

    m_context = eecontext;

    // compare versions
    peecb->GetVersion(&ulBuildVersion, EECBGV_GETBUILDVERSION);
    if (EECBGV_BUILDVERSION_MAJOR != (ulBuildVersion &
                                      EECBGV_BUILDVERSION_MAJOR_MA SK))
        return S_FALSE;  // oops!

    switch (eecontext)
    {
     case EECONTEXT_PROPERTYSHEETS:
     case EECONTEXT_SENDNOTEMESSAGE:
     case EECONTEXT_SENDPOSTMESSAGE:
     case EECONTEXT_SENDRESENDMESSAGE:
     case EECONTEXT_READNOTEMESSAGE:
     case EECONTEXT_READPOSTMESSAGE:
     case EECONTEXT_READREPORTMESSAGE:
         hr = S_OK;
        break;

     default:
         hr = S_FALSE;
        break;
    }


    return hr;

}
//
// end of CDGExt Object
// ----------------------------------------------------------------

The next section of code in the CDGEXT32.CPP file is the code that implements the methods of the IExchExtMessageEvents interface. Listing 12.7 shows the initial QueryInterface function for the object.


Listing 12.7. The initial QueryInterface function for the MessageEvents object.
// ----------------------------------------------------------------
// Handle creation of Messge Event object
// ----------------------------------------------------------------
//
// Routines for the Message Event extension
//
// CDGExtMsgEvents::QueryInterface() - [not used]
// CDGExtMsgEvents::OnRead() - at start of read msg
// CDGExtMsgEvents::OnReadComplete() - at end of read
// CDGExtMsgEvents::OnWrite() - ad start of write msg
// CDGExtMsgEvents::OnWriteComplete() - at end of write
// CDGExtMsgEvents::OnSubmit() - at start of msg submit
// CDGExtMsgEvents::OnSubmitComplete() - end of submit
// CDGExtMsgEvents::OnCheckNames() - start of resolve
// CDGExtMsgEvents::OnCheckNamesComplete() - resolve end
// ------------------------------------------------------------------

//
// sample queryinterface for message events
// not used now
//
STDMETHODIMP CDGExtMsgEvents::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{

    *ppvObj = NULL;
    if (riid == IID_IExchExtMessageEvents)
    {
        *ppvObj = (LPVOID)this;
        AddRef(); // using one more!
        return S_OK;
    }
    if (riid == IID_IUnknown)
    {
        *ppvObj = (LPVOID)m_pExchExt;  // return parent interface
        m_pExchExt->AddRef();
        return S_OK;
    }
    return E_NOINTERFACE;
);

Tip
The Windows Messaging client does not call the QueryInterface() methods directly. The code is added here in anticipation of changes to the client that might result in calls to this method. It is also a good rule to code the QueryInterface method for all COM objects.

Listing 12.8 shows the code for the OnRead and OnReadComplete methods. These methods check for the existence of the CheckSum property and, if found, compare the stored value to a freshly computed value. If the results do not match, the user is warned that the message may have been altered.


Listing 12.8. Code for the OnRead and OnReadComplete methods.
//
// fires when user attempts to Read a message
//
HRESULT CDGExtMsgEvents::OnRead(LPEXchEXTCALLBACK lpeecb)
{
  HRESULT hr;
  LPMESSAGE pMsg;
  LPMDB pMDB;
  ULONG ulCheckSum;
  LPSPropTagArray pNamedPropTags;
  LPSPropValue pPropValues;
  ULONG ulcValues;
  HWND hWnd;
  HCURSOR hOldCursor;

  m_hrOnReadComplete = S_FALSE; // assume all will go fine

  pMsg = NULL;
  pMDB = NULL;
  pPropValues = NULL;
  pNamedPropTags = NULL;

  // turned OFF?
  if (!bSignatureOn)
  {
      goto error_return;
  }

  // get the message
  hr = lpeecb->GetObject(&pMDB, (LPMAPIPROP *)&pMsg);
  if (FAILED(hr))
  {
    goto error_return;
  }

  // save current state of window and cursor
  lpeecb->GetWindow(&hWnd);
  hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

  // check to see if MsgChecksum exists
  NamedID[0].lpguid = (LPGUID)&IID_IMessage;
  NamedID[0].ulKind = MNID_STRING;
  NamedID[0].Kind.lpwstrName = L"MsgChecksum";

  hr = pMsg->GetIDsFromNames(1, (LPMAPINAMEID *)&NamedID, 0, &pNamedPropTags);
  if (FAILED(hr)) // maybe not there first time
  {
      goto error_return;
  }

  hr = pMsg->GetProps(pNamedPropTags, 0, &ulcValues, &pPropValues);
  if (FAILED(hr))    // must be some other error
  {
    goto error_return;
  }

  if (hr == MAPI_W_ERRORS_RETURNED) // not signed
  {
    goto error_return;
  }

  // must be signed, check the signature
  hr = CheckMsgSignature(pMsg, &ulCheckSum);
  if (FAILED(hr))
  {
    ErrMsgBox(hWnd, hr, "OnRead",
        "An error occured while calculating\n"
        "the message signature.");
      goto error_return;
  }

  //
  // got a value back
  //
  if (pPropValues[0].Value.ul == ulCheckSum)
  {
      MessageBox(hWnd,
          "Signed Message Verified.",
          "CDG Message Signing Extension",
          MB_OK);
  }
  else
  {
   int nRet = MessageBox(hWnd,
       "Signed Message was altered.\n"
       "Do you wish to view the message anyway?",
       "CDG MEssage Signing Extension",
       MB_YESNO);

   if (nRet == IDNO)
   // tell OnReadComplete to not display the message
      m_hrOnReadComplete = MAPI_E_CALL_FAILED;
  }

  error_return:

  hr = S_FALSE;

  //
  // free up resources
  //
  if (pMDB != NULL)
      pMDB->Release();

  if (pMsg != NULL)
      pMsg->Release();

  if (pNamedPropTags != NULL)
      MAPIFreeBuffer(pNamedPropTags);

  if (pPropValues != NULL)
      MAPIFreeBuffer(pPropValues);

  SetCursor(hOldCursor);

  return hr;
}

//
// fires after the read is complete
//
HRESULT CDGExtMsgEvents::OnReadComplete(LPEXchEXTCALLBACK lpeecb, ULONG ulFlags)
{

  return m_hrOnReadComplete;
}

Listing 12.9 shows the code for the OnWrite and OnWriteComplete methods. These methods are called when the user attempts to write a message back to the message store. This is the routine that computes a checksum signature and places it in a new message field before sending it to the recipient(s).


Listing 12.9. Coding the OnWrite and OnWrite complete methods.

//
// fires when user starts to write message
//
HRESULT CDGExtMsgEvents::OnWrite(LPEXchEXTCALLBACK lpeecb)
{
  HRESULT hr;

  hr = S_FALSE;

  return hr;
}

//
// fires after message has been written
//
HRESULT CDGExtMsgEvents::OnWriteComplete(LPEXchEXTCALLBACK lpeecb, ULONG ulFlags)
{
  HRESULT hr;
  LPMESSAGE pMsg;
  LPMDB pMDB;
  ULONG ulCheckSum;
  SPropValue pChksumProp[1];
  LPSPropTagArray pNamedPropTags;
  LPSPropProblemArray pPropProblems;
  HWND hWnd;
  HCURSOR hOldCursor;

  // turned OFF?
  if (!bSignatureOn)
  {
      return S_FALSE;
  }

  // just saving or really sending?
  if (!m_bInSubmitState)
  {
      return S_FALSE;
  }

  pMsg = NULL;
  pMDB = NULL;
  pPropProblems    = NULL;
  pNamedPropTags = NULL;

  hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));


  hr = lpeecb->GetObject(&pMDB, (LPMAPIPROP *)&pMsg);
  if (FAILED(hr))
  {
    goto error_return;
  }

  hr = CheckMsgSignature(pMsg, &ulCheckSum);
  if (FAILED(hr))
  {
    lpeecb->GetWindow(&hWnd);

    ErrMsgBox(hWnd, hr, "OnWriteComplete",
        "An error occured while calculating\n"
        "the message signature.");
    goto error_return;
  }

  // use a named property for the message body checksum
  NamedID[0].lpguid = (LPGUID)&IID_IMessage;
  NamedID[0].ulKind = MNID_STRING;
  NamedID[0].Kind.lpwstrName = L"MsgChecksum";

  hr = pMsg->GetIDsFromNames(1, (LPMAPINAMEID *)&NamedID, MAPI_CREATE, Â&pNamedPropTags);
  if (FAILED(hr))
  {
   goto error_return;
  }

  pChksumProp[0].ulPropTag = PROP_TAG(PT_LONG, HIWORD(pNamedPropTags-Â>aulPropTag[0]));
  pChksumProp[0].dwAlignPad = 0L;
  pChksumProp[0].Value.ul = ulCheckSum;

  hr = pMsg->SetProps(1, pChksumProp, &pPropProblems);
  if (FAILED(hr))
  {
   goto error_return;
  }


  // all fine, keep going
  hr = S_FALSE;

  error_return:


  //
  // free up resources
  //
  if (pMDB != NULL)
    pMDB->Release();

  if (pMsg != NULL)
      pMsg->Release();

  if (pNamedPropTags != NULL)
      MAPIFreeBuffer(pNamedPropTags);

  if (pPropProblems != NULL)
      MAPIFreeBuffer(pPropProblems);

  SetCursor(hOldCursor);

  return hr;
}

The last bit of code for the Message Events interface is found in Listing 12.10. Not much is happening here. This code is included for completeness.


Listing 12.10. The remaining Message Event object code.
//
// fires when user attempts to submit a msg to the outbox
//
HRESULT CDGExtMsgEvents::OnSubmit(LPEXchEXTCALLBACK lpeecb)
{
  HRESULT hr;

  hr = S_FALSE;
  m_bInSubmitState = TRUE;  // submit is called

  return hr;
}

//
// fires after message was submitted
//
VOID CDGExtMsgEvents::OnSubmitComplete(LPEXchEXTCALLBACK lpeecb, ULONG ulFlags)
{

  m_bInSubmitState = FALSE;  // out of submit state

}

//
// fires when user attempts to resolve addreses
//
HRESULT CDGExtMsgEvents::OnCheckNames(LPEXchEXTCALLBACK lpeecb)
{
  return S_FALSE; // not used
}

//
// fires after resolve is done
//
HRESULT CDGExtMsgEvents::OnCheckNamesComplete(LPEXchEXTCALLBACK lpeecb, ULONG ÂulFlags)
{
  return S_FALSE; // not used
}

The last section of code that deals with objects is the code for the IExchExtPropertySheets interface. Listing 12.11 shows the code for implementing custom property sheets for the Windows Messaging client.


Listing 12.11. Code for the custom property sheet objects.
// ----------------------------------------------------------------
// Handle creation CDGExtPropSheets object
// ----------------------------------------------------------------
//
// Routines for the propertysheet extension
//
// CDGExtPropSheets::QueryInterface() - [not used]
// CDGExtPropSheets::GetMaxPageCount() - count pages
// CDGExtPropSheets::GetPages() - add a new page
// CDGExtPropSheets::FreePages() - free up resources
// --------------------------------------------------------------



//
// sample queryinterface for propertysheets
// not used right now
//
STDMETHODIMP CDGExtPropSheets::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{

    *ppvObj = NULL;
    if (riid == IID_IExchExtPropertySheets)
    {
        *ppvObj = (LPVOID)this;
        // Increase usage count of this object
        AddRef();
        return S_OK;
    }
    if (riid == IID_IUnknown)
    {
        *ppvObj = (LPVOID)m_pExchExt;  // return parent interface
        m_pExchExt->AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;

}


//
// get property page count
//
ULONG CDGExtPropSheets::GetMaxPageCount(ULONG ulFlags)
{
ULONG ulNumExtSheets;

    switch (ulFlags)
    {
     // ignore these objects.
     case EEPS_FOLDER:
     case EEPS_STORE:
     case EEPS_MESSAGE:
         ulNumExtSheets = 0;
         break;

     // add a tab to the Tools|Options page
     case EEPS_TOOLSOPTIONS:
        ulNumExtSheets = 1;
        break;

     default:
         ulNumExtSheets = 0;
        break;
    }

    return ulNumExtSheets;
}


//
// to add a new page
//
STDMETHODIMP CDGExtPropSheets::GetPages(LPEXchEXTCALLBACK peecb,
                      ULONG ulFlags, LPPROPSHEETPAGE ppsp, ULONG FAR * pcpsp)
{
 LPMDB pMDB = NULL;
 LPMESSAGE pItem = NULL;

    *pcpsp = 0;


    // members for the new property page
    ppsp[0].dwSize = sizeof (PROPSHEETPAGE);
    ppsp[0].dwFlags = PSP_DEFAULT | PSP_HASHELP;
    ppsp[0].hInstance = ghInstDLL;
    ppsp[0].pszTemplate = MAKEINTRESOURCE(IDD_SIGNATURE);
    ppsp[0].hIcon = NULL;     // not used
    ppsp[0].pszTitle = NULL;  // not used
    ppsp[0].pfnDlgProc = (DLGPROC)MsgSigningDlgProc;
    ppsp[0].lParam = 0;
    ppsp[0].pfnCallback = NULL;
    ppsp[0].pcRefParent = NULL; // not used

    *pcpsp = 1;

    return S_OK;
}


//
// free up any resources
//
VOID CDGExtPropSheets::FreePages(LPPROPSHEETPAGE ppsp, ULONG ulFlags, ULONG cpsp)
{
   // not used
}
//
// end of CDGExtPropSheets object
// ----------------------------------------------------------------

Only the code for the local helper functions remains. This code section contains two routines: an error message handler and the code that actually computes the signature checksum. Listing 12.12 shows both these routines.


Listing 12.12. The ErrMsgBox and CheckMsgSignature routines.
// ------------------------------------------------------------------
// Local Helper routines
// ------------------------------------------------------------------
//
// ErrMsgBox() - used to handle error msgs
// CheckMsgSignature() - calculates checksum
// ------------------------------------------------------------------

//
// ErrMsgBox()
//
//    Params:
//      hWnd       - parent window
//      hr         - HRESULT value (0 to suppress)
//      szFunction - function name in which the error occurred (NULL to suppress)
//      szMessage  - error message (required)
//
void ErrMsgBox(HWND hWnd, HRESULT hr, LPSTR szFunction, LPSTR szMessage)
{
 static char szError[256];

 if (szMessage == NULL)
 {
    MessageBox(hWnd,
         "An unknown error occured in\nextension",
         "CDG Message Signing Extension", MB_ICONEXCLAMATION | MB_OK);
    return;
 }

 if ((hr == 0) && (szFunction == NULL))
 {
    MessageBox(hWnd, szMessage, "CDG Message Signing Extension Error", ÂMB_ICONEXCLAMATION | MB_OK);
    return;
 }


 if (szFunction != NULL)
 {
  wsprintf(szError, "Error %08X in %s\n%s", hr, szFunction, szMessage);
  MessageBox(hWnd, szError, "CDG Message Signing Extension Error", ÂMB_ICONEXCLAMATION | MB_OK);
 }

}



//
// CheckMsgSignature(pMsg, &ulCheckSum)()
//
//    Params:
//       pMsg         - points to message object
//       *pulCheckSum - points to checksum
//
HRESULT CheckMsgSignature(LPMESSAGE pMsg, ULONG *pulCheckSum)
{
 HRESULT hr;
 LPSTREAM pStreamBody;
 ULONG ulValue;
 ULONG ulRead;
 LARGE_INTEGER LgInt;
 ULARGE_INTEGER uLgInt;

 //
 // make sure you have a valid msg body
 //
 if ( (pMsg == NULL) ||(pulCheckSum == NULL) )
 {
      hr = MAPI_E_INVALID_PARAMETER;
      goto error_return;
 }

 //
 // access the message body
 //
 pStreamBody = NULL;
 hr = pMsg->OpenProperty(PR_BODY, &IID_IStream, STGM_DIRECT | STGM_READ, 0, Â(LPUNKNOWN *) &pStreamBody);
 if (FAILED(hr))
 {
     goto error_return;
 }

 //
 // point to starting position
 //
 LgInt.LowPart = 0;
 LgInt.HighPart = 0;
 pStreamBody->Seek(LgInt, STREAM_SEEK_SET, &uLgInt);

 //
 // add up ascii values
 //
 (*pulCheckSum) = 0;
 ulValue = 0;
 while ( (S_OK == (hr = pStreamBody->Read((LPVOID)&ulValue, 4, &ulRead)))  &&
         (ulRead > 0) )
 {
    (*pulCheckSum) += ulValue;
    ulValue = 0;
 }


 //
 // clean up
 //
 error_return:

 if (pStreamBody != NULL)
     pStreamBody->Release();

 return hr;

}
//
// End of Helper routines
// ------------------------------------------------------------------

That is the end of the code for the CDGEXT32.CPP file. In the next section, you'll create the dialog box that will be added to the collection of property pages for the Windows Messaging client.

Laying Out and Coding the Property Sheet Dialog Box

The next step is to design the dialog page that will appear on the Tools | Options tabbed dialog box of the Windows Messaging client. Although you'll build this as if it were a standalone dialog box, Microsoft Exchange will use the information to create an additional tab in the property pages displayed by the Windows Messaging client.

The dialog box consists of a frame control (IDC_STATIC), a check box (IDC_ENABLESGN), and a label control (IDC_STATIC). Figure 12.3 shows the layout of the dialog box.

Figure 12.3 : Laying out the Property Sheet dialog box.

Once the dialog box is designed and saved into the CDGEXT32.RC, you need to create a code module to handle the user events on the property sheet. Listing 12.13 shows the code for the CDGPRP32.CPP file that handles all the dialog messages.


Listing 12.13. Code for the CDGPRP32.CPP file.
// ================================================================
//  CDGPRP32.CPP
// ================================================================
//

#include "CDGEXT32.H"

// ----------------------------------------------------------------
// MsgSigningDlgProc
//
//    Params:
//      hDlg   - handle to modeless dialog, the property page
//      uMsg   - message
//      wParam - wParam of wndproc
//      lParam - lParam of wndproc, points to NMHDR for notifications
//
// Handles events for the custom property page
// ----------------------------------------------------------------

BOOL CALLBACK MsgSigningDlgProc(HWND hDlg, UINT uMsg,
        WPARAM wParam, LPARAM lParam)
{
 BOOL bMsgResult;
 static hbRUSH hBrush;
 static COLORREF GrayColor;
 static LPNMHDR pnmhdr;
 static HWND hWndPage;

 switch (uMsg)
 {

  case WM_INITDIALOG:
  {
   LOGBRUSH lb;

    GrayColor = (COLORREF)GetSysColor(COLOR_BTNFACE);

    memset(&lb, 0, sizeof(LOGBRUSH));
    lb.lbStyle = BS_SOLID;
    lb.lbColor = GrayColor;
    hBrush = CreateBrushIndirect(&lb);

    return TRUE;
  }
  break;

  case WM_CTLCOLORDLG:
  case WM_CTLCOLORBTN:
  case WM_CTLCOLORSTATIC:

      if (hBrush != NULL)
    {
      SetBkColor((HDC)wParam, GrayColor);

      return (BOOL)hBrush;
    }

  break;

  case WM_DESTROY:
  {
   if (hBrush != NULL)
           DeleteObject(hBrush);


   return TRUE;
  }

  case WM_COMMAND:
  {
   if (LOWORD(wParam) == IDC_ENABLESGN)
   {
      SendMessage(GetParent(hDlg), PSM_chANGED, (WPARAM)hDlg, 0L);
      bSignatureOn = SendDlgItemMessage(hDlg, IDC_ENABLESGN, BM_GETchECK, 0, 0L);
   }
  }
  break;

  case WM_NOTIFY:
  {
   pnmhdr = ((LPNMHDR) lParam);

   switch ( pnmhdr->code)
   {
    case PSN_KILLACTIVE:
        bMsgResult = FALSE;  // allow this page to receive PSN_APPLY
        break;

    case PSN_SETACTIVE:

        // initialize controls
        if (bSignatureOn)
            SendDlgItemMessage(hDlg, IDC_ENABLESGN, BM_SETchECK, 1, 0L);
        else
            SendDlgItemMessage(hDlg, IDC_ENABLESGN, BM_SETchECK, 0, 0L);

        hWndPage = pnmhdr->hwndFrom;   // to be used in WM_COMMAND

        bMsgResult = FALSE;
        break;

    case PSN_APPLY:

        // get user input
        bSignatureOn = SendDlgItemMessage(hDlg, IDC_ENABLESGN, BM_GETchECK, 0, 0L);

        bMsgResult = PSNRET_NOERROR;
        break;

    case PSN_HELP:
        MessageBox( pnmhdr->hwndFrom,
                    "CDG Message Signing Extension\n"
                    "(c)1996 MCA/SAMS Publishing",
                    "About",
                    MB_OK);
        bMsgResult = TRUE;
        break;


    default:
        bMsgResult = FALSE;
        break;
   }  // switch

      SetWindowLong( hDlg, DWL_MSGRESULT, bMsgResult);
      break;

  }      // case WM_NOTIFY

  default:
      bMsgResult = FALSE;
    break;

 }  // switch


 return bMsgResult;
}

This is the end of the code for the project. If you have Microsoft Visual C++ (or some other compatible C compiler), you can compile this code to create the DLL extension that will be installed into the Windows Messaging client.

Those who cannot (or choose not to) compile the source code shown here can copy the CDGEXT32.DLL from the CD-ROM that ships with the book to your local hard drive instead. You can then install this version of the extension.

Installing and Testing the Message Signing Extension

There are two main steps to installing the compiled version of the Message Signing Extension. First, you should compile the project or produce the CDGEXT32.DLL and copy that to a folder on your local hard drive.

Note
If you are not compiling your own version of the source code, copy the CDGEXT32.DLL from the CD-ROM to a local directory on your machine.

Next, you need to add an entry to your system registry database to inform Microsoft Exchange that a new extension is available. Listing 12.14 shows the exact registry entry you need to add to the HKEY_LOCAL_MAchINE\Software\Microsoft\Exchange\Client\Extensions section of your registry database.


Listing 12.14. The registry entry for the CDGEXT32.DLL.
CDGEXT32 = 4.0;d:\sams\cdg\chap12\cdgext32\CDGEXT32.dll;1;00000111111100

Note
The exact drive and directory should reflect the correct location of the CDGEXT32.DLL on your system.

Once you've added the entry, close the registry to update it and then start the Windows Messaging client to force it to install the new extension. Now you're ready to try the new Windows Messaging client feature!

Running the Message Signing Extension

When you start up the Windows Messaging client and select Tools | Options from the main menu, you should see a new property page for the message signing feature (see Figure 12.4).

Figure 12.4 : Viewing the new Message Singing property page.

While you have the property page up, make sure that Message Signing is enabled (that is, the check mark is on).

Next, send yourself a new message. When the new message is written, the CDGEXT32.DLL kicks in and creates a checksum signature for the message. When you receive this message and open it, you should see a dialog box telling you the message was verified (see Figure 12.5).

Figure 12.5 : The CDGEXT32.DLL in action.

And that's all there is to it! You now have a working Windows Messaging client extension that can perform simple security checks on incoming messages. And you can turn the verification feature on and off using the new property page you created.

Summary

In this chapter, you learned the theory behind the Windows Messaging client extension model and the advantages of using Microsoft Exchange extensions instead of building your own standalone MAPI client. You also learned the following key concepts:

You also learned how to register Microsoft Exchange extension DLLs in the system registry (for 32-bit systems) or the EXchNG.INI files for 16-bit systems. You also learned that you can install an extension as a shared resource on the network by making the correct entry in the SHARED32.INI file (32-bit systems) or the SHARED.INI file (16-bit systems).

Finally, you learned the details about the methods and properties of the Message Events and Property Sheets COM interfaces, and used that knowledge to build a working Message Signing extension.