Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An MFC extension library to enable DLL plug-in technology for your application using MESSAGE_MAPs

0.00/5 (No votes)
8 Jun 2004 6  
A plug-in architecture which allows you to write plug-in DLLs for your application and extend/modify its functionality.

The example application includes all currently developed plug-in sources

Sample Image - main.gif

Table of content

Overview

In the past I have written a plug-in architecture for one of my MFC projects. This architecture, although it worked, was limited in what it could provide, in that the executable/DLL had to know about one another to a certain degree. I presented a sub set of this method in a previous article Exporting a Doc/View from a dynamically loaded DLL. What I wanted to do was provide a streamlined and consistent plug-in architecture to allow any MFC app to be converted across with ease. I have also had requests to post an article on the subject. That's how this library came about.

How does this library work?

The library is an MFC extension DLL which when linked to, provides a set of base classes which need to be derived from in your MFC application. There are classes for Application, Mainframe, document, view(s), dialog and plug-in map. If you derive your MFC project objects from these library ones, then by default they gain the plug-in architecture which allows you to expand/modify their standard operations by providing additional MESSAGE_MAPs, menu options and accelerators. In fact the library itself can easily be extended to cover additional window types, for example, if you made all your CEdit objects inherit from a base class equivalent to those in the library, you could make all your edit controls have plug-in features as well. I just didn't take the class library that far.

When a DLL supplies a plug-in for one of the executable classes it does this as a MESSAGE_MAP, which is constructed in exactly the same manner as those created by Class Wizard. All these plug-in message maps derive from the base class CPlugInMap, so to create a new plug-in you would derive a new class from this in a DLL.

When a plug-in enabled object type is created, the library queries all the loaded plug-in message map objects to see whether they are a plug-in for the object type in question:

// example function from the CPIView class

void CPIView::InitialisePlugIns()
{
    CPlugInApp *pApp = static_cast&ltCPlugInApp*&gt(AfxGetApp());
    // get our pointers to any plug in maps

    m_pMaps = pApp->GetMessageMaps(this, m_MapCount);
}

CPlugInMap** CPlugInApp::GetMessageMaps(CCmdTarget *pObj, int &count)
{
    count = 0;
    CRuntimeClass    *pClass = pObj-&gtGetRuntimeClass();    
        // get the class name to find plug in maps for

    
    // for each loaded DLL see whether a message map for 

    // this class has been defined

    POSITION pos = CPlugInMap::GetHeadPosition();
    while (pos)
    {
        if (CPlugInMap::GetAt(pos).m_pClass-&gtIsPlugInFor(pClass))
        {
            // its a match, count it

            count++;
        }
        CPlugInMap::MoveNext(pos);
    }
    CPlugInMap    **pMaps = NULL;
    if (count > 0)
    {
        // now return the list of CPlugInMap* pointers

        pMaps = new CPlugInMap*[count];            // allocate pointer array

        pos = CPlugInMap::GetHeadPosition();
        int    index = 0;
        while (pos)
        {
            if (CPlugInMap::GetAt(pos).m_pClass-&gtIsPlugInFor(pClass))
            {
                CPlugInMap *pMap = CPlugInMap::GetAt(pos).m_pClass-&gtCreateMapObject();
                pMap-&gtSetPlugInFor(pObj);
                ASSERT(index < count);            
                   // found more than the last time around!

                pMaps[index] = pMap;
                index++;
            }
            CPlugInMap::MoveNext(pos);
        }
    }
    return pMaps;
}

This means that every plug-in object must override the virtual function bool CPlugInMap::IsPlugInFor(CRuntimeClass *pClass) and use the CRuntimeClass* object passed in to see whether it is an object type it is a plug-in for. In the MDI tab example provided this is done like this:

bool CMFPlugIn::IsPlugInFor(CRuntimeClass *pClass)
{
    return (_tcscmp(pClass-&gtm_lpszClassName, _T("CMainFrame")) == 0);
}

In this case, our plug-in map is a plug-in for the CMainFrame class object. A plug-in can be used for many different objects, for example, the owner drawn menu plug-in just returns true for all plugable objects so that it can handle all the menu drawing for them.

Pre and Post calls to your plug-in maps

A message map which has a matching entry for the message being processed, will be called twice, once before the regular message map class (the Pre call), and once after (the Post call). This allows you to do code before and/or after the message being processed by the regular application supplied function (if there is one), or in the Pre call you can choose to suppress the message entirely, so that the regular application message map function is not called at all.

Library change (V1.4) - If you suppress a message in the Pre function, then no Post message handlers in your own or any other plug-ins will be called.

You may need to make sure, your code is only executed once, so you can check whether it is the Pre or Post call to your plug-in message map object. The IsPreCall() and IsPostCall() member functions allow you to check which call is being performed.

WARNING: In the case of mapping WM_CREATE you would normally use the Post version of your method as you may be wanting to create additional windows, and in such a case you need an already valid window to have been created to be the parent of the new windows.

Menu's and Accelerators

To add support for new menu items or accelerator keys, the library allows every plug-in DLL to export two functions:

MergeMenu        // when exported is used to merge menus

MergeAccelerator // when exported is used to merge accelerator tables

To add extra menu/accelerator items, you would create a DLL menu resource/accelerator with the extra items and this menu/accelerator would be merged into those used by the document template of the object you want to modify:

// example MergeMenu from MDI tabs example

extern "C" void MergeMenu(CMyMultiDocTemplate *pTemplate)
{
    ASSERT(pTemplate != NULL);
    // by default we merge our menu commands with all menu's!

    CMenu    docMenu;
    CMenu    append;

    docMenu.Attach(pTemplate-&gtm_hMenuShared);
    append.LoadMenu(IDR_TABBARMENU);
    VERIFY(PIMergeMenu(&docMenu, &append, true));
    docMenu.Detach();
}

The above example adds all the menu commands which are present if the IDR_TABBARMENU to all CDocTemplate objects and the CMainFrame default menu used when no documents are open. It is possible at this point to check which CDocTemplate object you want to add the menu items to. This would be done by calling the CMyMultiDocTemplate::GetDocClass() function and comparing the class name as required (the default CMainFrame object does not have a class name).

Merging accelerators works exactly the same, except that you would call the PIMergeAccelerator(HACCEL& hDestination, HACCEL hToMerge) function.

Adding additional document types to your application

The library also supports the addition of new document types to the application. If the DLL exports the functions:

GetDLLDocTemplateCount // returns the number of exported document types

GetDLLDocTemplate      // returns the CMyMultiDocTemplate pointer(s)

During the application initialization phase, your application calls the plug-in library function RegisterDLLDocumentTypes() which looks at all the loaded plug-in DLLs and adds any supplied document types:

void CPlugInApp::RegisterDLLDocumentTemplates()
{
    CMyMultiDocTemplate *pDocTemplate = NULL;
    
    for (int i = 0; i &lt m_PlugInDLLCount; ++i)
    {
        for (int j = 0; 
              j &lt m_pPlugInDLLs[i].GetDocTemplateCount(); ++j)
        {
            pDocTemplate = m_pPlugInDLLs[i].GetDocTemplate(j);
            ASSERT(pDocTemplate);            
               // DLL's said it had one, but didn't supply it!

            AddDocTemplate(pDocTemplate);
        }
    }
}

So your DLL would supply all the Doc/View/Child frame object types used by your new document type.

It should also be noted that if a plug-in DLL supplied a new document/view class which derived from the plug-in architecture, then these plug-in DLLs could also have plug-in maps/menus/accelerators supplied for them as well!

WARNING : The biggest problem you will have is that you must make sure that added menu commands and resources have unique ID numbers across all your DLL/EXE projects in use. If not, then you could find multiple commands being executed from a single menu option!

Well that's the general stuff taken care of.

Making your application plug-in enabled

To get the plug-in architecture to work for any window, you need to inherit from the correct base class (listed later). These base classes have overridden either one or both of the virtual functions OnCmdMsg(...) and OnWndMsg(...) declared in the MFC. In these new functions, we query any loaded plug-ins for additional message maps that need to be considered in the execution process. If they are found, then an object of that type is created and added to the list of plug-ins for that window/document. This allows the plug-ins to persist their state between messages.

There are some differences between the plug-in map and the regular one

When program execution reaches your plug-in map function, the this pointer which you see is a pointer to your CPlugInMap inherited class object. After all, your plug-in needs to keep track of state information about what it's doing, so it can access its own information, and can also query the CPlugInMap::m_pPlugInFor member variable, which is a CCmdTarget* pointer to the object that this is a plug-in for. If you need to access the actual object type and its members, you will have to cast the pointer to the correct object type, such as:

CMyObject* pMyObject = static_cast<CMyObject*>(m_pPlugInFor);
// asserts if cast failed, its not the right type of object!

ASSERT(pMyObject);

The plug-in classes

To get the plug-in architecture to work for a new application/mainframe/document/view/dialog, you need to derive your standard MFC class from the correct plug-in object type. These are:

  • CPlugInApp: The main application plug-in which has the additional code to load the plug-in DLLs.

    You need to inherit your CWinApp derived application class from this and add the following code to your class's CYourApp::InitInstance() function to enable the plug-in architecture:

    // InitInstance code
    
    
    // Load standard INI file options (including MRU)
    
    LoadStdProfileSettings();  
    
    // Load the plug-in DLL's.
    
    // This needs to be done before any document templates 
    
    // are registered and before the mainframe is created
    
    // as the plug-in DLL's can hook the mainframe message. 
    
    // Creation is a required message to hook
    
    // to allow additional toolbars and 
    
    // floating windows etc to be added.
    
    LoadPlugInDLLs();
    ReplaceDocManager();
    
    // Register the application's document 
    
    // templates.  Document templates
    
    // serve as the connection between 
    
    // documents, frame windows and views.
    
    
    CMyMultiDocTemplate* pDocTemplate;
    pDocTemplate = new CMyMultiDocTemplate(
        IDR_APPPLUTYPE,
        RUNTIME_CLASS(CAppPlugInCoreDoc),
        RUNTIME_CLASS(CChildFrame),
        RUNTIME_CLASS(CAppPlugInCoreView));
        AddDocTemplate(pDocTemplate);
    
    // now register any plug-in DLL document templates
    
    RegisterDLLDocumentTemplates();
    
    // create main MDI Frame window
    
    CMainFrame* pMainFrame = new CMainFrame;
    if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
    {
        return FALSE;
    }
    
    m_pMainWnd = pMainFrame;
    UpdateMenus();
    UpdateAccelerators();
    InitialisePlugIns(); // this does it for the application object only
    
    
    // Parse command line for standard shell 
    
    //commands, DDE, file open
    
    CCommandLineInfo cmdInfo;
    ParseCommandLine(cmdInfo);
    

    To enable plug-ins for the other objects in your application, you must call InitialisePlugIns() in the inherited class object's constructor. This procedure queries the loaded plug-in DLL's for any plug-in maps which can be used for an object of that type (be it, mainframe, documents, view etc...). These matches are performed on a class name basis.

    By default the application looks for the plug-in DLL'S in the PlugIns sub-directory of your application's executable location. Please make sure that this directory exists in your debug/release and installation directories and that the plug-in DLLs are located there!

    This class also provides the following additional function(s) in its public interface:

    • CString GetApplicationPath() - returns the location of the application executable - also used internally to locate the plug-in DLLs

    • int GetPlugInDLLCount() const; - Returns the number of loaded plug-in DLLs

    • CDLLWrapper* GetDLL(int index); - Returns a CDLLWrapper* pointer to a loaded DLL.

    You also need to add the line:

    #include "PlugInLib.h"
    
    

    to your applications stdafx.h file to automatically link to the library and use the exported classes.

  • CPIMainframe - Inherit your CMainFrame object from this class.
  • CPIChildframe - Inherit your CChildFrame object(s) from this class.
  • CPIDoc - Inherit your CDocument object(s) from this class.
  • CPIView - Inherit your CView object(s) from this class.
  • CPIScrollView - Inherit your CScrollView object(s) from this class.
  • CPIFormView - Inherit your CFormView object(s) from this class.
  • CPIDialog - Inherit your CDialog object(s) from this class.

You will also have to make any dialog class have run time class information available by adding these lines:

// to .h file

DECLARE_DYNCREATE(CYourDialogClass)
// to .cpp file

IMPLEMENT_DYNCREATE(CYourDialogClass, CPIDialog)

For all of these class types, you will need to call InitialisePlugIns() in the constructor of your inherited class. Why? Because the plug-in architecture does the matching using the class name and due to the rules of inheritance and the way the RUNTIME_CLASS information works, this will not be setup correctly until all the bases classes have been fully constructed, so you would not get the right class name when checking if a class needed a plug-in map.

Creating a plug-in DLL

To create a new plug-in DLL for your now enabled plug-in application, use the VS option to create a new project of the type MFC extension DLL.

  1. Once the project has been created add the line:
    #include "PlugInLib.h"

    to the stdafx.h file.

  2. Add a post build step

    I also like to add a post build step to the DLL's build procedure, which copies the newly compiled version of the DLL to the target application's DLL plug-in directory: copy Debug\SomeDLL.dll ..\TargetApplication\debug\PlugIns

    Example post build step in visual studio

    This is done so that your application will always use the latest version during debugging etc.

  3. Create a plug-in MESSAGE_MAP object

    Hmm, for some reason Visual Studio does not make this an easy option, so here is a copy of the boiler-plate code used to create a new plug-in MESSAGE_MAP object.

    // Some plug-in header
    
    //
    
    //////////////////////////////////////////////////////////////////////
    
    
    #if !defined(NEED_A_NEW_DEFINE)
    #define NEED_A_NEW_DEFINE
    
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    
    
    class CMyNewPlugInMapObject : public CPlugInMap  
    {
    public:
        DECLARE_DYNCREATE(CMyNewPlugInMapObject)
    
        CMyNewPlugInMapObject();
        CMyNewPlugInMapObject(bool special);
        virtual ~CMyNewPlugInMapObject();
    
    // Attributes
    
    public:
    // Operations
    
    public:
    
    // Overrides
    
        // ClassWizard generated virtual function overrides
    
        //{{AFX_VIRTUAL(CMFPlugIn)
    
        //}}AFX_VIRTUAL
    
        virtual CPlugInMap*     CreateMapObject();
        virtual bool            IsPlugInFor(CRuntimeClass *pClass);
    
    // Implementation
    
    protected:
    
        // Generated message map functions
    
        //{{AFX_MSG(CMyNewPlugInMapObject)
    
        //}}AFX_MSG
    
    
        DECLARE_MESSAGE_MAP()
    };
    
    #endif // !defined(NEED_A_NEW_DEFINE)
    
    

    And for the .cpp file:

    // Some plug-in implementation of the CMyNewPlugInMapObject class.
    
    //
    
    //////////////////////////////////////////////////////////////////////
    
    
    #include "stdafx.h"
    
    #include "boiler_plate.h"
    
    
    //////////////////////////////////////////////////////////////////////
    
    // Construction/Destruction
    
    //////////////////////////////////////////////////////////////////////
    
    CMyNewPlugInMapObject plugIn(true);
    
    IMPLEMENT_DYNCREATE(CMyNewPlugInMapObject, CPlugInMap)
    
    CMyNewPlugInMapObject::CMyNewPlugInMapObject()
    {
    
    }
    
    CMyNewPlugInMapObject::CMyNewPlugInMapObject(bool special)
    {
        AddObject();
    }
    
    CMyNewPlugInMapObject::~CMyNewPlugInMapObject()
    {
    
    }
    
    BEGIN_MESSAGE_MAP(CMyNewPlugInMapObject, CPlugInMap)
        //{{AFX_MSG_MAP(CMyNewPlugInMapObject)
    
        //}}AFX_MSG_MAP
    
    END_MESSAGE_MAP()
    
    //////////////////////////////////////////////////
    
    // CMyNewPlugInMapObject message handlers
    
    
    CPlugInMap* CMyNewPlugInMapObject::CreateMapObject()
    {
        return new CMyNewPlugInMapObject;
    }
    
    bool CMyNewPlugInMapObject::IsPlugInFor(CRuntimeClass *pClass)
    {
        return (_tcscmp(pClass-&gtm_lpszClassName, _T("ClassToPlugInFor")) == 0);
    }
    

    Copies of this code can be found in the library. See the files boiler_plate.h and boiler_plate.cpp.

    Once you have an object created and registering with the library, it should become active. Just add regular MESSAGE_MAP entries as you would, in a regular MFC project to handle the messages you wish to.

    Note the object being declared at the beginning of the file:

    CMyNewPlugInMapObject plugIn(true);

    This global version of the object is used to register the plug-in map object with the library. Without it, your plug-in would not be called.

Additional functions to export

Depending on what features you need in your application, your plug-in DLLs will need to export any of the following functions to enable the features you need.

  • MergeMenu(CMyMultiDocTemplate* pTemplate)

    This function is used by the framework to add additional menu items into the application's document template menus. If your DLL adds new functions, you may need to expose menu options for them. This is where you add them. In your DLL create the resource template for the menu items you wish to add. You would then export this function from your DLL by adding a global function to it like this:

    //extern "C" void __declspec(dllexport) 
    
    // MergeMenu(CMyMultiDocTemplate *pTemplate)
    
    extern "C" void MergeMenu(CMyMultiDocTemplate *pTemplate)
    {
        ASSERT(pTemplate != NULL);
        // Note that the default menu for 
    
        // the mainframe will have a GetDocClass of ""
    
        if (_tcscmp(L"CAppPlugInCoreDoc", pTemplate->GetDocClass()) == 0)
        {
            CMenu docMenu;
            CMenu append;
    
            docMenu.Attach(pTemplate->m_hMenuShared);
            append.LoadMenu(IDR_MENU1);
            // merge in the new menu items
    
            VERIFY(PIMergeMenu(&docMenu, &append, true));
            // make sure the menu is not 
    
            // destroyed as its owned by the document template
    
            docMenu.Detach();
        }
    }
    

    If you do not export directly using __declspec(dllexport), you need to add a line to the DLL project's .DEF file:

    ; PlugIn1.def : Declares the module parameters for the DLL.
    
    LIBRARY      "SomePlugIn"
    DESCRIPTION  'SomePlugIn Windows Dynamic Link Library'
    
    EXPORTS
       ; Explicit exports can go here
    MergeMenu
    
  • MergeAccelerator(CMyMultiDocTemplate* pTemplate)

    This function is used by the framework to add additional accelerator items into the application's document template accelerator. If your DLL adds new functions, they may have accelerator keys for them. This is where you add them. In your DLL create the resource template for the accelerator items you wish to add.

    //extern "C" void __declspec(dllexport) 
    
    // MergeAccelerator(CMyMultiDocTemplate *pTemplate)
    
    extern "C" void MergeAccelerator(CMyMultiDocTemplate *pTemplate)
    {
        ASSERT(pTemplate != NULL);
        // Note that the default accelerator for 
    
        //the mainframe will have a GetDocClass of ""
    
        if (_tcscmp(L"CAppPlugInCoreDoc", 
                pTemplate->GetDocClass()) == 0)
        {
            HACCEL hMerge = LoadAccelerators(hDLLInstance, 
                MAKEINTRESOURCE(IDR_ACCELERATOR1));
            // did the accelerator fail to be loaded? 
    
            //Does it exist
    
            ASSERT(hMerge);
            VERIFY(PIMergeAccelerator(pTemplate->m_hAccelTable, hMerge));
            DestroyAcceleratorTable(hMerge);
        }
    }
    

    Again, you may need to add this line to your projects .DEF file in the exports section:

    MergeAccelerator

    Note : As mentioned above, make sure that your new menu options etc have unique IDs across EXE and DLLs!

  • InitialiseDLL(CWinApp *pApp)

    This procedure is called by the framework, after all the plug-in DLLs have been loaded to allow then to initialise any variables etc that they may need to. Put your DLL setup code in this exported function.

  • ReleaseDLL()

    As your application shuts down, this function will be called just before the framework unloads your plug-in DLL. You should release all dynamically allocated objects and resources by the end of this procedure if you have any, in your application.

  • GetDLLDocTemplateCount()

    Use this function to return the number of additional document templates supported by the plug-in DLL.

  • GetDLLDocTemplate(int index)

    Use this function to return the document templates that will be registered with the application. This will allow your plug-in application to support extra document types. After all you might as well make full use of that MDI interface!

Additional debugging notes

When you need to debug your application/DLLs, the best way I have found is to insert the DLL projects into the workspace of the application. You can then also add the DLLs in the application's projects settings to the Debug:Additional DLLs section, to include your plug-in DLLs directly. As the VC debugger will know about them all at start-up time, you will be able to set breakpoints in both your application code and your DLL code.

Example additional DLLs in visual studio to allow DLL breakpoints

You should also set the DLL projects as dependencies of the EXE so that when you build, the versions available will always be up to date.

Making your application load faster

Your application and its plug-ins will load faster, if you re-base each of the DLLs in your project(s) so that they do not clash in memory usage. Every time they do, windows will relocate the offending DLL and this takes time. You can tell when its happening in debug mode as you get a message such as:

LDR: Automatic DLL Relocation in AppPlugInCore.exe
LDR: Dll m_res.dll base 10000000 relocated due to collision with 
    E:\Personal\CodeProject\Projects\AppPlugInCore\Debug\PlugInLibrary.dll

Its easy enough to change, just go to the DLL's project settings in the Link tab and select the Output category. Choose a base address that will not cause it to overlap with an existing DLL in your project.

Example re-basing a DLL in a Visual Studio DLL project

Other notes

The library should be UNICODE compatible. I just haven't tested it, but where strings manipulation/comparison code is used, I have tried to use the _tcs... version which will compile to the correct code under UNICODE or a standard ANSI application.

There are still some areas of this library that need extra extension. Off the top of my head the following should be added. People who can provide the functions to do this will be highly praised.

  • There is no support for serialization, so your plug-ins cannot persist their data. I am thinking of doing this by providing a virtual function in CPlugInMap which is called by the CPIDoc::Serialize() as a post option only. This will allow them to tag their data onto the end of the archive, with associated checking to make sure it's your data when loading. In fact a similar method should be used for all types of virtual functions, it's just a very large amount of work that I do not have time for, right this minute. :(
  • Not all of the major plug-in classes have been 100% checked at this time.

Problems encountered

  • When I set out to develop this library I started on my home PC which runs Windows 98, I then continued this code on my work PC, which runs Windows NT. At this point I kept getting a run time error in Samuel Gonzalo's CFileFinder extension class because GetLongPathName() was not present in NT's version of kernel32.dll. In the end I just commented this section from CFileFinder as I was not making use of it.
  • Nasty casting - To get the OnWndMsg plug-in code to work, I had to do a nasty cast 102 times, 1 for each of the window's message map prototypes which are called in the function CPlugInApp::CallWindowMessageMap(), as my CPlugInMap object inherits from CCmdTarget and not CWnd like the compiler is expecting. This just gets around the problem. As far as I can tell, there is no danger in doing it this way, as execution passes directly across to the message map function without any problems.
  • When it came to merging accelerator tables, a search showed no articles which covered this. So after a little bit of head scratching and perseverance with the MSDN, I wrote a really quick and simple function to do it. :-D
  • The main code of the library has been through about 3 iterations of modification, where some of the core functionality was changed.
  • I never seemed to have enough time to work on this, or play CounterStrike.

Code acknowledgements, references and thanks

Whilst constructing this library I used code/information from the following articles:

Thanks goes to

Update History

The library and example(s) have been through the following changes:

V1 3rd October 2002

Initial release.

V1.1 8th October 2003

Several CP members reported a problem when using the library on Windows XP/Me where the libarry crashes during the close down of the application. It took a compiler upgrade on my work XP machine before I was able to reproduce the problem. Once I could do that, I then had to work around the fact that JIT debugging does not work on my PC, so after using AllocConsole and WriteFile to allow me to trace the execution path, I found that when the CMainFrame object recieves the WM_NCDESTROY message, it calls delete this on itself! So when the the library tried to call the Post message handler on any plug-in maps, they were in fact invalid as they had already been deleted. A code clean-up in the destructor of this and all the other classes (just in case!) solves the problem.

V1.2 9th May 2004

I reworked the plug-in destruction code to better solve the shut down problem experienced by early users of the class. Where before I was relying on the NULL pointers etc not to be called, this was not quite good enough. I made use of the method suggested by Brian (H) in the comments section below. Doing this fully cleared up any close/shut down problems I have seen with the library.

Additional work was done on the method message supression and plug-in use. This was so that the owner-drawn menu plug-in would work correctly. to upgrade any existing plug-ins to use the new library version, you have to change any code that reads:

// header

virtual LPCTSTR GetClass();

// c++ file

LPCTSTR CMFPlugIn::GetClass()
{
    // return the name of the class which this 

    // is a plug in map for, e.g. "CMyApp"

    return L"CMainFrame" ;
}

to

// header

virtual bool IsPlugInFor(CRuntimeClass *pClass);

// c++ file

bool CMFPlugIn::IsPlugInFor(CRuntimeClass *pClass)
{
    return (_tcscmp(pClass-&gtm_lpszClassName, L"CMainFrame") == 0);
}

This allows a plug-in to work for multiple target classes.

V1.3 21st May 2004

During the development of the "Enhanced print preview" plug-in, a flaw in the message suppression was found. This occurs when you need to suppress a message and a hidden message pump occurs during functions called by the plug-in map message handler. To fix the problem, I needed to introduce a suppression stack which handles the saving of the message suppression state on a per message basis. This has no knock on effect to any existing plug-ins.

V1.4 7th June 2004

While developing the "Single Instance" plug-in, a problem with the message suppression was found. The library needed to be changed such that whenever a plug-in suppressed a message in the Pre handler, then no Post plug-in handlers get called. This required changes to all classes which implement the virtual overrides OnCmdMsg() and onWndMsg().

Enjoy!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here