一、温故而知新—MFC框架
1)RTTI & Dynamic Create
简要说明:
----------------------------------------------------------------------------------------------
以上图用于说明MFC两项关键技术:RTTI(Runtime Type Information) 运行时类型识别和Dynamic Create对象动态生成。
实现机理:
CRuntimeClass该结构为MFC内建类型,其主要作用:
定义一个用户类型时,利用该结构记录该用户类型名和对象创建函数地址。系统中所有的用户类型信息通过该结构保存起来,并形成“类型识别网”(实际上就是链表)。
如果一个用户类型的.h和.CPP文件中加入了DECLARE_DYNCREATE()和IMPLEMENT_DYNCREATE()宏,则相当于:
1.创建了一个的CRuntimeClass结构类型的全局实例(名为:class##类名)。
2.该实例记录了该用户类型的两项重要信息:
用户类型名(m_lpszClassName)
对象创建函数指针(m_pfnCreateObject)
3.利用指针m_pBaseClass指向基类的“类型识别对象”(classBaseClass),利用m_pNextClass指向下一个“类型识别对象”,这样就将该用户类型的“类型识别对象”加入到全局的“类型识别网”(链表)中。
4
.“类型识别网”(链表)的首指针为
:CRuntimeClass::pFirstRuntimClass
,此指针变量为全局类型。系统通过该全局指针遍历“类型识别网”(链表)。
RTTI
类型识别:
遍历“类型识别网”(链表),比较
CRuntimeClass
类型指针或类型名
lpszClassName).
如:
pObj->IsKindOf(RUNTIME_CLASS(CMyClass));
Dynamic Create
动态创建:
遍历“类型识别网”(链表),通过比较类型名(
m_lpszClassName
),找到对应的对象创建函数指针
(m_pfnCreateObject)
,动态创建对象,返回“基类指针”。(
创建对象时,只需“类型识别网”中提供的信息,不依赖任何类型信息)
以上与实际的应用可能存在细节上的出入,但大致的原理应该是这样。
2)Message Mapping
传统SDK程序的消息循环
在传统的SDK程序中,消息循环是很简单的, 在WinMain 中 CreateWindow通过一个参数将创建 的 窗口和窗口类联系起来,这样该窗口的所有消息都将发送到该窗口类的窗口函数WndProc,其后
WndProc根据不同的消息给予不同的动作。
MFC期望的消息循环
在传统的SDK程序中消息循环是非常简单的,并且将窗口和窗口函数绑定在一起。而在MFC中就出现了 问题,比如CDocument类,不是窗口,所以没有窗口类,但是我也想让它响应消息,怎办?问题不仅仅 如此,我们再看看MFC的消息,就会发现更多问题。
MFC
将消息分为三大类:
1
.标准消息,如
WM_
开头,任何派生自
CWnd
的类都可以接受该消息
2
.命令消息,即
WM_COMMAND
,任何派生自
CCmdTarget
的类,兼可接受该消息。
3
.通知类消息,
Control Notification
,也以
WM_COMMAND
形式出现,由控件产生,通知其
父窗口。
MFC中,所有能够接受消息的类都必须继承于CCmdTarget类,因为这些类都一个共同的特征:
含有DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP三个宏
(宏的定义,在MFC中中查看很方便,这里不具体列出),就这三个宏组织了一张庞大的消息映射
网。如下图:
从上图可以看出DECLARE_MESSAGE_MAP和BEGIN_MESSAGE_MAP和END_MESSAGE_MAP三个宏的作用,其实就是创建了两个全局数据结构:
“消息映射数组”:将消息和对应的处理函数记录下来。
“映射表”:通过AFX_MSGMAP中的pBaseMap指针,将各类按继承顺序连接起来,从而形成消息映射网,以提供消息流动的道路。
我们已经建立了一张消息流动网络,但是消息是怎样从产生到响应函数收到该消息?
不管怎么说,对
Windows
系统来说都是一样的,它都是不断地用
GetMessage
(或者其它)从消息队列中取出消息,然后用
DispatchMessage
将消息发送到窗口函数中去。在
"
窗口类的诞生
"
中知道,
MFC
将所有的窗口处理函数都注册成
DefWndProc
,那是不是
MFC
将所有的消息都发送到
DefWndProc
中去了呢?很抱歉不是,而是都发送到了
AfxWndProc
函数去了。你可能要问为什么,这也是我想知道的,那我们就看看下面的序列图中窗口的创建过程吧:
消息的起点是
AfxWndProc
函数,所有的消息都被发送到
AfxWndProc
,也从
AfxWndProc
再次流向各自的消息响应函数的,怎么流的呢?看看下图就
知道了:
如果是“标准消息(WM_XXXX)”,则在CWnd::OnWndMsg函数内,直接发送到接收消息的目标窗口。
如果是"通知消息",则转到CCmdTarget::OnCmdMsg函数中(其内的GetMessageMap为虚函数),对自身的映射表中查找,简图如下:
图1 消息的拐弯流动
--------------------
二、自动化(Automation)对象———组件技术应用。
自动化,是基于COM技术的一个高级应用。
自动化的本质(最简单的理解):
是继承了IDispatch接口的接口。
说到底,自动化就是为了消除编译期的类型信息依赖性(COM也有CLSID/IID/tlb/h依赖性),
而通过“名字”来晚期(运行时)绑定函数地址和参数类型校验的技术。通常的COM就是通过内存中虚表来晚期帮定,但其编译时仍然需要导入类型信息(#import).
有了自动化这一技术后,在脚本语言和解释执行语言中,在不需要任何类型信息的情况下,就可以方便调用自动化对象。仅仅知道ProgID(字符串)就够了(ProgID = “组件名.类名”)
创建自动化对象:
在MFC中,为应用自动化技术提供了很大的便利,使用起来非常简单。
1。创建MFC的RegularDLL时,勾选"Automation"
2。创建一个用户类,继承于CCmdTarget。创建完后,MFC系统自动生成一个接口dispinterface,其信息保存在odl文件(与COM的idl文件类似),同时生成对应该接口的实现类,该类名与接口同名(接口与实现类是一对一关系)。他们之间的关联估计是通过宏或MFC内部机制来实现。
3。余下就是声明和实现接口,就这样,一个自动化对象创建完成了。
调用自动化对象:
在VC6.0下调用通常有两种方式:
1。COleDispatchDriver *pDriver = new ();
pDriver->CreateDispatch(组件名.类名);
pDirver->InvokeHelper(函数索引,DISPATCH_METHOD,。。。);
此调用不存在类型依赖性,而是通过索引或函数名实现晚绑定。
2。生成自动化对象的包装类(COleDispatchDriver).
在类创建向导窗口中,选择'Automation'页面,后点"Add Class...",选择你所包装自动化的tlb或DLL文件。 确定后,MFC系统自动生成一个继承于COleDispatchDriver的包装类,类名为tlb中的接口名。
MFC直接通过该包装类的函数,每个函数实际上还是调用InvokeHelper,转移到自动化对象中。
关键的地方是该包装类在调用CreateDispatch后,将自动化对象的接口指针保存到包装类的成员m_lpDispatch内,包装类就通过该成员进行调用转移。个人认为,该调用方式,仍然存在“tlb类型依赖性”。只是调用时避开了InvokeHelper那些麻烦参数,显得更简洁而已。
有点罗嗦。呵呵。
关于自动化,<ATL开发指南>书中,讲得清晰易懂.
---------------------
待续 ......