65.9K
CodeProject 正在变化。 阅读更多。
Home

一个MFC扩展库,用于通过MESSAGE_MAPS为您的应用程序启用DLL插件技术。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (43投票s)

2002 年 10 月 3 日

CPOL

17分钟阅读

viewsIcon

611929

downloadIcon

8663

一个插件架构,允许您为应用程序编写插件DLL,并扩展/修改其功能。

示例应用程序包含所有已开发的插件源代码

Sample Image - main.gif

目录

概述

过去,我曾为我的一个 MFC 项目编写了一个插件架构。这个架构虽然有效,但在功能上有所限制,因为可执行文件/DLL 在一定程度上需要相互了解。我在一篇之前的文章《从动态加载的 DLL 中导出 Doc/View》中介绍了这个方法的一部分。我想要提供一个精简一致的插件架构,以便任何 MFC 应用程序都能轻松地进行转换。我还收到了一些关于发布该主题文章的请求。这就是这个库的由来。

这个库是如何工作的?

该库是一个 MFC 扩展 DLL,链接后提供了一组基类,您的 MFC 应用程序需要从中派生。有用于应用程序、主框架、文档、视图、对话框和插件映射的类。如果您从这些库类派生您的 MFC 项目对象,那么默认情况下它们就获得了插件架构,允许您通过提供额外的 MESSAGE_MAP、菜单选项和加速键来扩展/修改其标准操作。实际上,库本身可以轻松地扩展以覆盖其他窗口类型,例如,如果您让所有的 CEdit 对象继承自库中等效的基类,那么您就可以让所有的编辑控件也具备插件功能。我只是没有将类库扩展到那么远。

当 DLL 为可执行类之一提供插件时,它通过 MESSAGE_MAP 来实现,其构造方式与类向导创建的完全相同。所有这些插件消息映射都派生自基类 CPlugInMap,因此要创建新插件,您需要在 DLL 中从该基类派生一个新类。

当创建插件启用的对象类型时,该库会查询所有已加载的插件消息映射对象,以确定它们是否是针对当前对象类型的插件。

// example function from the CPIView class
void CPIView::InitialisePlugIns()
{
    CPlugInApp *pApp = static_cast<CPlugInApp*>(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->GetRuntimeClass();    
        // 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->IsPlugInFor(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->IsPlugInFor(pClass))
            {
                CPlugInMap *pMap = CPlugInMap::GetAt(pos).m_pClass->CreateMapObject();
                pMap->SetPlugInFor(pObj);
                ASSERT(index < count);            
                   // found more than the last time around!
                pMaps[index] = pMap;
                index++;
            }
            CPlugInMap::MoveNext(pos);
        }
    }
    return pMaps;
}

这意味着每个插件对象都必须重写 virtual 函数 bool CPlugInMap::IsPlugInFor(CRuntimeClass *pClass),并使用传入的 CRuntimeClass* 对象来判断它是否是其插件的对象类型。在提供的 MDI 标签示例中,它是这样实现的:

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

在这种情况下,我们的插件映射是 CMainFrame 类对象的插件。一个插件可以用于许多不同的对象,例如,所有者绘制菜单插件对所有可插件对象都返回 true,以便它可以处理所有菜单绘制。

调用您的插件映射的之前之后

具有与正在处理的消息匹配条目的消息映射将被调用两次,一次在常规消息映射类之前(Pre 调用),一次在之后(Post 调用)。这允许您在消息被常规应用程序提供的函数(如果存在)处理之前和/或之后执行代码,或者在Pre 调用中,您可以选择完全抑制该消息,这样常规应用程序消息映射函数就不会被调用。

库更改 (V1.4) - 如果您在Pre函数中抑制了消息,那么您自己或其他插件的Post消息处理程序将不会被调用。

您可能需要确保您的代码只执行一次,因此您可以检查您的插件消息映射对象是Pre调用还是Post调用。IsPreCall()IsPostCall() 成员函数允许您检查正在执行的调用。

警告:在映射 WM_CREATE 的情况下,您通常会使用方法的Post版本,因为您可能需要创建额外的窗口,在这种情况下,您需要一个已存在的有效窗口作为新窗口的父窗口。

菜单和加速键

为了添加对新菜单项或加速键的支持,该库允许每个插件 DLL 导出两个函数

MergeMenu        // when exported is used to merge menus
MergeAccelerator // when exported is used to merge accelerator tables

要添加额外的菜单/加速器项,您将创建一个包含额外项的 DLL 菜单资源/加速器,该菜单/加速器将被合并到您想要修改的对象文档模板所使用的菜单/加速器中。

// 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->m_hMenuShared);
    append.LoadMenu(IDR_TABBARMENU);
    VERIFY(PIMergeMenu(&docMenu, &append, true));
    docMenu.Detach();
}

上面的示例会将 IDR_TABBARMENU 中存在的所有菜单命令添加到所有 CDocTemplate 对象以及在没有文档打开时使用的 CMainFrame 默认菜单中。此时可以检查您想要添加菜单项的 CDocTemplate 对象。这可以通过调用 CMyMultiDocTemplate::GetDocClass() 函数并根据需要比较类名来完成(默认的 CMainFrame 对象没有类名)。

加速键的合并工作方式完全相同,只是您需要调用 PIMergeAccelerator(HACCEL& hDestination, HACCEL hToMerge) 函数。

为您的应用程序添加其他文档类型

该库还支持向应用程序添加新文档类型。如果 DLL 导出函数

GetDLLDocTemplateCount // returns the number of exported document types
GetDLLDocTemplate      // returns the CMyMultiDocTemplate pointer(s)

在应用程序初始化阶段,您的应用程序会调用插件库函数 RegisterDLLDocumentTypes(),该函数会查看所有已加载的插件 DLL 并添加任何提供的文档类型。

void CPlugInApp::RegisterDLLDocumentTemplates()
{
    CMyMultiDocTemplate *pDocTemplate = NULL;
    
    for (int i = 0; i < m_PlugInDLLCount; ++i)
    {
        for (int j = 0; 
              j < 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);
        }
    }
}

因此,您的 DLL 将提供新文档类型使用的所有 Doc/View/Child frame 对象类型。

还应该注意的是,如果插件 DLL 提供了一个派生自插件架构的新文档/视图类,那么这些插件 DLL 也可以为它们提供插件映射/菜单/加速键!

警告:您遇到的最大问题是,您必须确保添加的菜单命令和资源在所有正在使用的 DLL/EXE 项目中具有唯一的 ID 号。否则,您可能会发现单个菜单选项执行多个命令!

好了,一般的介绍就到这里。

使您的应用程序支持插件

要使插件架构适用于任何窗口,您需要继承正确的基类(稍后列出)。这些基类重写了 MFC 中声明的 OnCmdMsg(...)OnWndMsg(...) 虚拟函数之一或两者。在这些新函数中,我们查询任何已加载的插件,以获取在执行过程中需要考虑的附加消息映射。如果找到,则创建一个该类型的对象并将其添加到该窗口/文档的插件列表中。这使得插件可以在消息之间保持其状态。

插件映射和常规映射之间存在一些差异

当程序执行到达您的插件映射函数时,您看到的 this 指针是指向您的 CPlugInMap 继承类对象的指针。毕竟,您的插件需要跟踪其状态信息,因此它可以访问自己的信息,并且还可以查询 CPlugInMap::m_pPlugInFor 成员变量,它是一个指向此插件所关联对象的 CCmdTarget* 指针。如果您需要访问实际的对象类型及其成员,您将不得不强制转换指针到正确的对象类型,例如

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

插件类

要使插件架构适用于新的应用程序/主框架/文档/视图/对话框,您需要从正确的插件对象类型派生您的标准 MFC 类。它们是:

  • CPlugInApp:主应用程序插件,它包含加载插件 DLL 的附加代码。

    您需要从这个类派生您的 CWinApp 派生应用程序类,并在您的类的 CYourApp::InitInstance() 函数中添加以下代码以启用插件架构:

    // 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);
    

    为了启用应用程序中其他对象的插件,您必须在继承类对象的构造函数中调用 InitialisePlugIns()。此过程会查询已加载的插件 DLL 是否有可用于该类型对象(无论是主框架、文档、视图等)的插件映射。这些匹配是基于类名进行的。

    默认情况下,应用程序会在应用程序可执行文件位置的 PlugIns 子目录中查找插件 DLL。请确保在您的调试/发布和安装目录中存在此目录,并将插件 DLL 放置在那里!

    此类还提供以下附加函数在其 public 接口中

    • CString GetApplicationPath() - 返回应用程序可执行文件的路径 - 也用于内部定位插件 DLL

    • int GetPlugInDLLCount() const; - 返回已加载的插件 DLL 的数量

    • CDLLWrapper* GetDLL(int index); - 返回指向已加载 DLL 的 CDLLWrapper* 指针。

    您还需要添加以下行:

    #include "PlugInLib.h"
    

    到您的应用程序的 stdafx.h 文件中,以便自动链接到库并使用导出的类。

  • CPIMainframe - 从此类派生您的 CMainFrame 对象。
  • CPIChildframe - 从此类派生您的 CChildFrame 对象。
  • CPIDoc - 从此类派生您的 CDocument 对象。
  • CPIView - 从此类派生您的 CView 对象。
  • CPIScrollView - 从此类派生您的 CScrollView 对象。
  • CPIFormView - 从此类派生您的 CFormView 对象。
  • CPIDialog - 从此类派生您的 CDialog 对象。

您还需要使任何对话框类具备运行时类信息,添加以下行:

// to .h file
DECLARE_DYNCREATE(CYourDialogClass)
// to .cpp file
IMPLEMENT_DYNCREATE(CYourDialogClass, CPIDialog)

对于所有这些类类型,您都需要在派生类的构造函数中调用 InitialisePlugIns()。为什么?因为插件架构使用类名进行匹配,并且由于继承规则和 RUNTIME_CLASS 信息的工作方式,在所有基类完全构造完毕之前,此信息不会正确设置,因此在检查类是否需要插件映射时,您将无法获得正确的类名。

创建插件DLL

要为现在启用插件的应用程序创建新的插件 DLL,请使用 VS 选项创建一个名为MFC extension DLL的新项目。

  1. 项目创建后,添加以下行:
    #include "PlugInLib.h"

    stdafx.h 文件中。

  2. 添加一个生成后步骤

    我喜欢在 DLL 的生成过程中添加一个生成后步骤,它会将新编译的 DLL 复制到目标应用程序的 DLL 插件目录:copy Debug\SomeDLL.dll ..\TargetApplication\debug\PlugIns

    Example post build step in visual studio

    这样做是为了让您的应用程序在调试等过程中始终使用最新版本。

  3. 创建插件 MESSAGE_MAP 对象

    嗯,Visual Studio 出于某种原因并不提供这个选项,所以这里是用于创建新插件 MESSAGE_MAP 对象的样板代码的副本。

    // 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)
    

    而用于.cpp文件:

    // 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->m_lpszClassName, _T("ClassToPlugInFor")) == 0);
    }
    

    这些代码的副本可以在库中找到。请参见文件 boiler_plate.hboiler_plate.cpp

    一旦您创建并注册了一个对象到库中,它就会变得活跃。只需像在常规 MFC 项目中一样添加常规 MESSAGE_MAP 条目来处理您想要的任何消息。

    注意文件开头声明的对象:

    CMyNewPlugInMapObject plugIn(true);

    该对象的全局版本用于将插件映射对象注册到库中。没有它,您的插件将不会被调用。

要导出的附加函数

根据您的应用程序需要哪些功能,您的插件 DLL 需要导出以下任何函数以启用您需要的功能。

  • MergeMenu(CMyMultiDocTemplate* pTemplate)

    该函数由框架用于将附加菜单项添加到应用程序的文档模板菜单中。如果您的 DLL 添加了新功能,您可能需要为其公开菜单选项。这就是您添加它们的地方。在您的 DLL 中,创建您想要的菜单项的资源模板。然后,您可以通过向其添加一个全局函数来导出此函数,如下所示:

    //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();
        }
    }
    

    如果您不直接使用 __declspec(dllexport) 导出,则需要在 DLL 项目的 .DEF 文件中添加一行:

    ; 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)

    该函数由框架用于将附加加速器项添加到应用程序的文档模板加速器中。如果您的 DLL 添加了新功能,它们可能具有相应的加速键。这就是您添加它们的地方。在您的 DLL 中,创建您想要的加速器项的资源模板。

    //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);
        }
    }
    

    同样,您可能需要在项目的.DEF文件的导出部分中添加此行:

    MergeAccelerator

    注意:如上所述,请确保您的新菜单选项等在 EXE 和 DLL 中具有唯一的 ID!

  • InitialiseDLL(CWinApp *pApp)

    在所有插件 DLL 加载完成后,框架会调用此过程,允许它们初始化所需的任何变量等。将您的 DLL 设置代码放入此导出的函数中。

  • ReleaseDLL()

    当您的应用程序关闭时,将在框架卸载您的插件 DLL 之前调用此函数。如果您的应用程序中有任何动态分配的对象和资源,您应该在此过程结束时释放它们。

  • GetDLLDocTemplateCount()

    使用此函数返回插件 DLL 支持的附加文档模板的数量。

  • GetDLLDocTemplate(int index)

    使用此函数返回将要注册到应用程序的文档模板。这将允许您的插件应用程序支持额外的文档类型。毕竟,为什么不充分利用 MDI 界面呢!

其他调试说明

当您需要调试应用程序/DLL 时,我发现最好的方法是将 DLL 项目添加到应用程序的工作区中。然后,您还可以将 DLL 添加到应用程序的项目设置的Debug: Additional DLLs部分,以直接包含您的插件 DLL。由于 VC 调试器在启动时就会了解它们,因此您可以在应用程序代码和 DLL 代码中设置断点。

Example additional DLLs in visual studio to allow DLL breakpoints

您还应该将 DLL 项目设置为 EXE 的依赖项,这样在生成时,可用的版本将始终是最新的。

加快您的应用程序加载速度

您的应用程序及其插件的加载速度会更快,如果您重新基址化项目中的每个 DLL,使它们不占用冲突的内存。每次发生这种情况时,Windows 都会重新定位冲突的 DLL,这会花费时间。在调试模式下,当您收到类似以下消息时,您会知道何时发生:

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

更改起来很容易,只需转到 DLL 项目设置的Link选项卡,然后选择Output类别。选择一个不会与项目中现有 DLL 重叠的基地址。

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

其他注意事项

该库应该是 UNICODE 兼容的。我只是没有测试过,但在使用字符串操作/比较代码的地方,我尝试使用 _tcs... 版本,它将在 UNICODE 或标准 ANSI 应用程序下编译为正确的代码。

该库仍有一些领域需要进一步扩展。我能想到的有以下几点。能够提供相关功能的贡献者将受到高度赞扬。

  • 不支持序列化,因此您的插件无法持久化其数据。我正在考虑通过在 CPlugInMap 中提供一个虚拟函数来实现此目的,该函数由 CPIDoc::Serialize() 作为仅后置选项调用。这将允许它们将数据附加到存档的末尾,并进行相关检查以确保在加载时是您的数据。事实上,对于所有类型的虚拟函数,应该使用类似的方法,只是工作量很大,我目前没有时间完成。 :(
  • 并非所有主要的插件类都已 100% 检查。

遇到的问题

  • 当我开始开发这个库时,我是在我的家用 PC 上进行的,它运行 Windows 98。然后我在我的工作 PC 上继续编写代码,它运行 Windows NT。此时,我不断收到 Samuel Gonzalo 的 CFileFinder 扩展类中的运行时错误,因为 GetLongPathName() 在 NT 版本 的 kernel32.dll 中不存在。最后,我只是注释掉了 CFileFinder 的这一部分,因为我没有使用它。
  • 糟糕的强制类型转换 - 为了使 OnWndMsg 插件代码正常工作,我不得不在 CPlugInApp::CallWindowMessageMap() 函数中进行 102 次糟糕的强制类型转换(每个窗口的消息映射原型一次),因为我的 CPlugInMap 对象继承自 CCmdTarget 而不是编译器期望的 CWnd。这只是绕过了问题。据我所知,这样做没有危险,因为执行会直接传递到消息映射函数,没有任何问题。
  • 在合并加速器表时,搜索显示没有涵盖此问题的文章。所以,在稍微思考和研究 MSDN 后,我编写了一个非常快速简单的函数来实现它。 :-D
  • 库的主体代码已经经历了大约 3 次修改迭代,其中一些核心功能被更改了。
  • 我似乎总是不够时间来处理这个,或者玩《反恐精英》。

代码致谢、参考文献和感谢

在构建这个库的过程中,我使用了以下文章中的代码/信息:

感谢

更新历史

该库和示例已经过以下更改:

V1 2002 年 10 月 3 日

初始发布。

V1.1 2003 年 10 月 8 日

几位 CP 成员报告了在使用该库的 Windows XP/Me 上出现问题,导致应用程序关闭时库崩溃。我不得不升级了我工作 XP 机器上的编译器,才能够重现这个问题。一旦我能做到这一点,我就不得不绕过我的 PC 上 JIT 调试不起作用的事实,因此在使用 AllocConsoleWriteFile 允许我跟踪执行路径后,我发现当 CMainFrame 对象收到 WM_NCDESTROY 消息时,它会对自己执行 delete this!因此,当库尝试调用任何插件映射的 Post 消息处理程序时,它们实际上是无效的,因为它们已经被删除了。在它和其他所有类的析构函数中进行代码清理(以防万一!)可以解决问题。

V1.2 2004 年 5 月 9 日

我重构了插件的销毁代码,以更好地解决早期用户遇到的关闭问题。以前我依赖于 NULL 指针等不会被调用,这还不够好。我使用了 Brian (H) 在下面的评论部分建议的方法。这样做完全解决了我在库中看到的任何关闭/关机问题。

对消息抑制和插件使用的方法进行了额外的改进。这是为了让所有者绘制菜单插件能够正常工作。要将现有插件升级到使用新库版本,您必须更改任何读取以下代码的代码:

// 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->m_lpszClassName, L"CMainFrame") == 0);
}

这允许插件为一个或多个目标类工作。

V1.3 2004 年 5 月 21 日

在开发“增强型打印预览”插件的过程中,发现了消息抑制的一个缺陷。这发生在您需要抑制消息并且在插件映射消息处理程序调用的函数中发生隐藏消息循环时。为了修复这个问题,我需要引入一个抑制堆栈,该堆栈可以按消息保存消息抑制状态。这对任何现有插件都没有影响。

V1.4 2004 年 6 月 7 日

在开发“单实例”插件时,发现了消息抑制的一个问题。库需要更改,以便每当插件在Pre处理程序中抑制消息时,Post插件处理程序都不会被调用。这需要更改实现虚拟重写 OnCmdMsg()onWndMsg() 的所有类。

尽情享用!

© . All rights reserved.