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毫秒