Programming 版 (精华区)

发信人: SwordLea (飞刀李), 信区: Programming
标  题: 编码的境界——从MOC到COM(原创).9
发信站: 哈工大紫丁香 (2004年01月20日09:14:37 星期二), 站内信件

    对于已知事件扩充新功能,这种方式显得非常方便。如果希望让用户一眼就
看出程序增加了含金量,添加一个菜单也许是更好的选择。此时,我们需要操作
的就不仅是一个DLL函数管理器了,至少应该再有一个菜单管理器。
    新的菜单管理器类接口信息写入公共访问的Interface.h头文件中:
    // Interface.h
    class IMenuManager
    {
        // 加入菜单
        Add(LPCTSTR pszName, LPCTSTR pszSubName, UINT uID) = 0;

        // 删除菜单
        Del(UINT uID) = 0;

        // 通知菜单事件发生
        Notify(uID)   = 0;
    }

    各成员函数的操作都是围绕着App主框架的Menu。Add()时,判断上层菜单与
子菜单名是否存在,不存在则加入新菜单并设置新菜单ID。Del()时,查找该ID
的菜单是否存在,若存在则找到其Position,删除(此方法可以删除系统提供的
菜单)。重载CMainFrame类的OnCommand(),以WPama(菜单ID)为参数调用已经
注册了eventMenu事件的各函数指针。被调用的各函数都在DLL中,它们得到控制
权后,依据事先注册的ID判断执行哪个功能。
    如果考虑对现有程序的修改量最小,我们甚至可以这样简单修改App与Dll中
的代码。
    首先,管理器类(CDllFuncsManager)的基类加入IMenuManager,并实现各
纯虚函数,即:
    // DllFuncsManager.h
    class CDllFuncsManager :public IDllFuncsManager,
                            public IMenuManager
    {
        ……
    }
    其次,DLL导出函数得到参数后通过CDllFuncsManager类进行指针转换,即:
    #include "DllFuncsManager.h"
    int MyEntry(IDllFuncsManager *pDFM)
    {
        CDllFuncsManager *pDllManager = (CDllFuncsManager *)pDFM;
        IMenuManager *pMenu = (IMenuManager *)pDllManager;
        ……
    }
  这样,就得到了主框架中的菜单管理器指针。但这种方法违背了我们只暴露
接口、不接触细节的初衷。最好的方法还是为IDllFuncsManager接口提供一个新
的成员函数,即:

    // Interface.h
    class IDllFuncsManager
    {
        ……
        // 取IMenuManager 接口
        GetInterfaceMenu(LPVOID *ppMenu) = 0;
    }
  新增加的成员函数在CDllFuncsManager类实现时,只需要简单地传出this指
针即可。这样,DLL 导出函数不需要了解CDllFuncsManager类,就可以得到
IMenuManager指针,即:
    int MyEntry(IDllFuncsManager *pDFM)
    {
        IMenuManager *pMenu = NULL;
        pDFM->GetInterfaceMenu((LPVOID *)&pMenu);
        ……
    }

    2、主程序暴露给DLL机制。
  App 在得到Dll 导出函数入口以后,把自己主框架的CWnd类暴露给Dll ,两
者同样不需要了解对方的细节。这种方式更多的用在对主框架窗体风格的维护,
也就是类似于“换肤”。Dll在得到控制权和主框架的m_hWnd后,可以对其进行子
类化,拦截部分消息(比如WM_PAINT、WM_NCPAINT、WM_LBUTTONDOWN)进行处理。

    3、共同注册组件方式。
    如果我们的App只是维护一个单纯的主框架,假设是MFC应用程序向导生成的
DLG、SDI、MDI框架,而所有的函数都是以组件的方式存放于Dll中,主框架在一
些共性操作时,比如打开文件(用于判断文件格式是否支持)、序列化(进行文
件格式转换),查询到某组件的函数指针,然后对该指针进行调用,那么我们的
App 几乎可以永远不修改,只要根据不同需要,提供不同的Dll ,就可以满足对
各种文件的处理要求。这就意味着除了一些与功能密切相关的界面(就象某某功
能的设置什么的)以外,我们全心全意地去实现功能,再也不用理界面了。

    还记得我们前面在取IMenuManager接口时采用的方法么?就是这个:
    GetInterfaceMenu(LPVOID *ppMenu);

  若是我们有一个全局的组件管理器,想Get 什么,就可以Get 到什么,而且
与我们在哪里Get 无关,App 也罢,Dll 也罢,只要它存在,我们就能得到,那
世界该多么美好啊!BBS 里再也不会有帖子反复问“A 需要包含B 、B 又需要包
含A ”的问题了。
  一听到组件,有人会联想到本文的标题,联想到COM ,因而开始头痛,准备
挥手作别。唉,如果现在离开,也许他再也没机会看到关于COM 技术最最生动的
诠释。
  对于GetInterfaceMenu这个函数,也许你还心有余悸,相信C 语言关于指针
部分的章节是你整个大学生活中最难忘、最动听的摇篮曲。如今看到指针的指针,
恐怕你已经开始喊怕怕了,其实,这也是我用LPVOID* 而没有用void ** 的原因。
    不了解指针,也不想了解指针,这样的同学同样也可以使用指针,就象我们
不曾深入了解COM的机理,也同样可以使用COM。下面的几个“法则”可以使指针
变得“透明”,让我们不必去管指针与内存地址的对应关系:
    a、变量前面有个“*”的是指针;
    b、变量赋值给指针,变量前面加“&”;
    c、使用该指针成员后面加“->”,否则前面加“*”;
    d、指针也是变量,适用上面三条。
  这里使用了递归,也不是太严密,希望能帮助大家使用指针,举例解释一下:

    int n变量 = 5;
    int *p变量 = &n变量;       // 法则a,法则b
    ASSERT(*p变量 == n变量);   // 法则c
    int **pp变量 = &p变量;     // 法则a,法则b,法则d
    ASSERT(**pp变量 == n变量); // 法则c

    好了,四条法则(所谓法则,是实在没办法了才记的规则),五条语句,指针
的用法应该大致了解。

    让我们先设计一个组件管理器,因为该组件管理器是App与Dll都会经常访问的,
把它的接口定义在interface.h中:
    // Interface.h
    // 组件管理器接口
    class IComponentManager
    {
    public:
        typedef int (*PCOMPONENT)(const GUID& guid,LPVOID *pObject);

        // 注册组件工厂
        virtual int RegisterComponentFactory(PCOMPONENT pFactory) = 0;

        // 注销组件工厂
        virtual int UnRegisterComponentFactory(PCOMPONENT pFactory) = 0;

        // 获得组件指针
        virtual int CreateComponent(const GUID& guid,LPVOID *ppObject) = 0;

    }
    其实现思路如下:
    #include "interface.h"
    class ComponentManager : public IComponentManager
    {
        std::vector <PCOMPONENT> m_vFactory;    // 类厂表

        // 注册组件工厂
        int RegisterComponentFactory(PCOMPONENT pFactory)
        {
            // 如果该组件工厂已经存在,返回false
            m_vFactory.push_back(pFactory);
            return true;
        }

        // 注销组件工厂
        int UnRegisterComponentFactory(PCOMPONENT pFactory)
        {
            // 如果该组件工厂不存在,返回false
            m_vFactory.erase(pFactory);
            return true;
        }

        // 获得组件指针
        int CreateComponent(const GUID& guid,LPVOID *pObject)
        {
            for(int i = 0; i<m_vFactory.size() ;i++)
            {
                PCOMPONENT pFactory = m_v_Factory[i];

                if( pFactory(guid,pObject))
                {
                    return true;
                }
            }

            return false;
        }
    } ;
    现在我们以组件的方式实现菜单管理器,并写出其组件工厂:
    #include "Interface.h"
    class CMenuManager: public IMenuManager
    {
        // 加入菜单
        Add(LPCTSTR pszName, LPCTSTR pszSubName, UINT uID){}

        // 删除菜单
        Del(UINT uID) {}

        // 通知菜单事件发生
        Notify(uID)   {}
    } g_CMenuManager;

    // 菜单管理器组件工厂
    int CMenuManager(const GUID& guid,LPVOID *pObject)
    {
        if(memcmp(guid,IID_IMenuManager,sizeof(GUID)) == 0)
        {
            *pObject = (IMenuManager*)&g_CMenuManager;
            return true;
        }
        return false;
    }
    再加入该组件GUID到interface.h里。

    // 菜单管理器IID
    // {C39A1685-E330-4b08-AD75-ED49B5BE9AC8}
    static const GUID IID_IMenuManager =
    { 0xc39a1685, 0xe330, 0x4b08, { 0xad, 0x75, 0xed, 0x49, 0xb5, 0xbe, 0x9a, 
0xc8 } };

  我们可以在App 的CWinApp 派生类中加入ComponentManager的实例CM作为成
员变量,再声明一个全局IComponentManager 指针g_CM,在CWinApp 派生类的构
造函数中为该指针赋值。这样,无论在哪里,只要加入interface.h的包含, 并
声明 extern IComponentManager *g_CM,就可以使用g_CM得到你需要的组件,
而前提是你需要知道该组件的接口及其GUID。

  到这里,我们已经实现了一个COM 的雏形,相信即使我们永远不使用COM 也
再不会重蹈MOC 的覆辙。编码的境界其实远不只三种,在程序设计的道路上,我
们走过的路都还不算长,如果本文能给初学者一些启迪,能让他们少走些弯路,
我这两个月来没有早餐的清晨就不算白白浪费。

                                                 (the end)
 
--
    一天,八戒很伤心地问师父:“难道这个世界上真的是我最丑吗?”
师父脸色为难地说:“你还是问观音姐姐吧。”八戒问了观音后乐呵呵地
跑回来说:“师父,谁是artist啊?”

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.246.232]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:4.912毫秒