windows的消息宏定义

    MFC的设计者们在设计MFC时,紧紧把握一个目标,那就是尽可能使得MFC的代码要小,速度尽可能快。为了这个目标,他们使用了许多技巧,其中很多技巧体现在宏的运用上,实现MFC的消息映射的机制就是其中之一。
  
    同MFC消息映射机制有关的宏有下面几个:
  
    DECLARE_MESSAGE_MAP()宏
  
    BEGIN_MESSAGE_MAP(theClass, baseClass)和END_MESSAGE_MAP()宏
  
    弄懂MFC消息映射机制的最好办法是将找出一个具体的实例,将这些宏展开,并找出相关的数据结构。
  
    DECLARE_MESSAGE_MAP()
  
    DECLARE_MESSAGE_MAP()宏的定义如下:
  
    #define DECLARE_MESSAGE_MAP()
  
    private:
  
    static const AFX_MSGMAP_ENTRY _messageEntries[];
  
    protected:
  
    static AFX_DATA const AFX_MSGMAP messageMap;
  
    virtual const AFX_MSGMAP* GetMessageMap() const;
  
  从上面的定义可以看出,DECLARE_MESSAGE_MAP()作下面三件事:
  
    定义一个长度不定的静态数组变量_messageEntries[];
  
    定义一个静态变量messageMap;
  
    定义一个虚拟函数GetMessageMap();
  
  在DECLARE_MESSAGE_MAP()宏中,涉及到MFC中两个对外不公开的数据结构
  
  AFX_MSGMAP_ENTRY和AFX_MSGMAP。为了弄清楚消息映射,有必要考察一下这两个数据结构的定义。
  
    AFX_MSGMAP_ENTRY的定义
  
    struct AFX_MSGMAP_ENTRY
  
    {
  
     UINT nMessage; // windows message
  
     UINT nCode; // control code or WM_NOTIFY code
  
     UINT nID; // control ID (or 0 for windows messages)
  
     UINT nLastID; // used for entries specifying a range of control id’s
  
     UINT nSig; // signature type (action) or pointer to message #
  
     AFX_PMSG pfn; // routine to call (or special value)
  
    };
  
  结构中各项的含义注释已经说明得很清楚了,这里不再多述,从上面的定义你是否看出,AFX_MSGMAP_ENTRY结构实际上定义了消息和处理此消息的动作之间的映射关系。因此静态数组变量_messageEntries[]实际上定义了一张表,表中的每一项指定了相应的对象所要处理的消息和处理此消息的函数的对应关系,因而这张表也称为消息映射表。再看看AFX_MSGMAP的定义。
  
    (2)AFX_MSGMAP的定义
  
    struct AFX_MSGMAP
  
    {
  
     const AFX_MSGMAP* pBaseMap;
  
     const AFX_MSGMAP_ENTRY* lpEntries;
  
     };
  
  不难看出,AFX_MSGMAP定义了一单向链表,链表中每一项的值是一指向消息映射表的指针(实际上就是_messageEntries的值)。通过这个链表,使得在某个类中调用基类的的消息处理函数很容易,因此,“父类的消息处理函数是子类的缺省消息处理函数”就“顺理成章”了。在后面的“MFC窗口的消息处理”一节中会对此作详细的讲解。
  
    BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()
  
    它们的定义如下:
  
    #define BEGIN_MESSAGE_MAP(theClass, baseClass)
  
    const AFX_MSGMAP* theClass::GetMessageMap() const
  
    { return &theClass::messageMap; }
  
    AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap =
  
    { &baseClass::messageMap, &theClass::_messageEntries[0] };
  
    AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =
  
    {
  
     #define END_MESSAGE_MAP()
  
     {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
  
     };
  
  对应BEGIN_MESSAGE_MAP()的定义可能不是一下子就看得明白,不过不要紧,举一例子就很清楚了。对于BEGIN_MESSAGE_MAP(CView, CWnd),VC预编译器将其展开成下面的形式:
  
    const AFX_MSGMAP* CView::GetMessageMap() const
  
    {
  
     return &CView::messageMap;
  
     }
  
    AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CView::messageMap =
  
    {
  
     &CWnd::messageMap,
  
     &CView::_messageEntries[0]
  
    };
  
    AFX_COMDAT const AFX_MSGMAP_ENTRY CView::_messageEntries[] =
  
    {
  
    至于END_MESSAGE_MAP()则不过定义了一个表示映射表结束的标志项,我想大家对于这种简单的技巧应该是很熟悉的,无需多述。
  到此为止,我想大家也已经想到了象ON_COMMAND这样的宏的具体作用了,不错它们只不过定义了一种类型的消息映射项,看看ON_COMMAND的定义:
  
    #define ON_COMMAND(id, memberFxn)
  
    { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },
  
     根据上面的定义,ON_COMMAND(ID_FILE_NEW, OnFileNew)将被VC预编译器展开
  
     如下:
  
    {WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv,
  
    (AFX_PMSG)&OnFileNew},
  
  到此,MFC的消息映射机制已经清楚了,现在提出并解答两个问题以作为对这一节的小结。
  
    为什么不直接使用虚拟函数实现消息处理函数呢?这是一个GOOD QUESTION。前面已经说过,MFC的设计者们在设计MFC时有一个很明确的目标,就是使得“MFC的代码尽可能小,速度尽可能快”,如果采用虚拟函数,那么对于所有的窗口消息,都必须有一个与之对应的虚拟函数,因而对每一个从CWnd派生的类而言,都会有一张很大的虚拟函数表vtbl。但是在实际应用中,一般只对少数的消息进行处理,大部分都交给系统缺省处理,所以表中的大部分项都是无用项,这样做就浪费了很多内存资源,这同MFC设计者们的设计目标是相违背的。当然,MFC所使用的方法只是解决这类问题的方式之一,不排除还有其他的解决方式,但就我个人观点而言,这是一种最好的解决方式,体现了很高的技巧性,值得我们学习。
  
    至于这第二个问题,是由上面的问题引申出来的。如果在子类和父类中出现了相同的消息出来函数,VC编译器会怎么处理这个问题呢?VC不会将它们看作错误,而会象对待虚拟函数类似的方式去处理,但对于消息处理函数(带afx_msg前缀),则不会生成虚拟函数表vtbl。
  

c++cli

C++/CLI描绘的是一种多元组合,此处的 C++ 当然是指 Bjarne Stroustrup 在 Bell 实验室发明的C++编程语言。它支持速度和执行 文件的大小都得到优化的静态对象模型。但除了堆内存分配以外,它不支持运行时程序对对象的更改。它允许对底层机器进行无限制的访问,但对于正在运行的程序中的活动类型、以及相关的程序基础构造,它的访问能力却非常有限、或者根本就不可能。我在微软的同事 Herb Sutter,也是C++/CLI的主架构师,认为C++是一个混凝土语言。

“CLI”即公共语言基础结构(Common Language Infrastructure),这是一个支持动态组件编程模型的多层架构。在许多方面,它所表示的对象模型和C++的完全相反。它是一个运行时软件层 ,一个虚拟执行系统,运行在应用程序和底层操作系统之间。对底层机器的访问受到相当严格的限制。支持对运行中程序的活动类型以及关联程序的基础构造 进行存取——发现和建构。斜线“/”表示 C++ 和 CLI 之间的一种绑定(binding),有关这种绑定的细节构成本专栏的常规主题。

所以,对于“什么是C++/CLI?”这个问题第一个最近似的答案是:它是静态C++对象模型到动态CLI组件对象模型的一种绑定, 简言之,它就是你如何用C++进行.NET编程,而不是用C#或Visual Basic.NET。象C#和CLI自己一样,C++/CLI正在经历 ECMA(欧洲计算机制造商协会) 标准化并最终要经历ISO标准认证。

公共语言运行时(CLR)是微软版的CLI,专门用于 Windows 操作系统,同样,Visual C++ 2005是C++/CLI 的实现。

第二个近似答案是:我觉得C++/CLI在C++内集成.NET编程模型与以前贝尔实验室在当时的C++中用模板集成泛型编程一样有异曲同工之处。两种情况中,你在现有C++代码库上的投资以及你现有的C++专业技术都得到保护。这是C++/CLI设计的一个基本 要求。

学习C++/CLI

一种C++/CLI语言的设计有三个层面,这三个层面也适用于所有语言:语言层语法到公共类型系统(CTS) 的映射;选择为程序员直接操作而公开的底层CLI基本组织结构 的详细程度;以及选择要提供的超越CLI直接支持的附加功能。

第一个层面是所有CLI语言在很大程度上都共有的,第二个层面和第三层面是某一CLI语言区别于其它语言的地方。根据所要解决的问题,你可以选择某一 种语言,也可以将多种CLI语言结合起来。学习C++/CLI语言需要掌握这三个设计层面。

怎样将C++/CLI 映射到CTS?

了解底层CTS 对学习C++/CLI非常有帮助,它主要包括三个常规类类型:

多态引用类型,其用于所有的类继承;

非多态值类型,其用于实现需要运行时效率的具体类型,如数字类型;

抽象接口类型,其用于定义一个实现该接口的一组引用类型或值类型共同使用的公共操作集;

在设计方面,虽然CTS到一组内置的语言类型的映射对于所有CLI语言来说都是共同的,当然,每一种CLI语言的语法各不相同。例如,在C#中,我们可以 这样来定义一个抽象基类 Shape,从这个类派生特定的几何模型对象。

abstract class Shape { … } // C#

而在C++/CLI中,可以象下面这样写,以表示完全相同的底层引用类型:

ref class Shape abstract { … }; // C++/CLI

在底层 IL(中间语言)中,以上两种声明以完全相同的方式表示。同样,在C#中,我们可以用下面的代码来定义一个具体的 Point2D 类 :

struct Point2D { … } // C#

而在C++/CLI中写成:

value class Point2D { … }; // C++/CLI

借助 C++/CLI 支持的类类型家族表现了一种本机方式的 CTS 集成。它确定了你的语法选择,例如:

class native {};
value class V {};
ref class R {};
interface class I {};

CTS 也支持枚举类类型,其行为方式与本机枚举稍微有些区别,C++/CLI对二者都提供支持:

enum native { fail, pass };
enum class CLIEnum : char { fail, pass};

同样,CTS支持其自己的数组类型,其行为也与本机数组类型有一定差别,微软同样对二者提供支持:

int native[] = { 1,1,2,3,5,8 };
array<int>^ managed = { 1,1,2,3,5,8 };

那种认为任何一种 CLI 语言比另一种语言更接近或几乎就是到底层CLI的映射是不精确的。相反,每一种CLI语言都只是表达了自己对底层CLI对象模型的一种 见解。在下一节你将更清楚地看到这一点。

CLI 的细节标准

在设计CLI语言时必须考虑的第二个设计层面是要将什么程度的底层CLI实现模型结合到该语言中。这个语言解决什么样的问题?要解决这些问题必须要什么样的工具? 此外,该语言很可能吸引哪一类程序员?

下面,我们利用发生在托管堆中的值类型问题。在许多情况下,值类型可以在托管堆中找到自己:

通过隐式的框入/框出操作(boxing)——当值类型的某个实例被赋值给一对象时,或者通过某个未被改写的值类型调用一个虚拟方法时;

当值类型被当作为引用类类型的成员时;

当值类型被当作CLI数组元素存储时;

是否允许程序员处理这种值类型地址是设计CLI语言时必须要解决的问题。

存在的问题?

位于托管堆中的任何对象在垃圾回收器进行清扫收缩的过程中都有可能遭遇重新分配,指向这些对象的任何指针必须被追踪并在运行时得到更新,而程序员 无法自己手动追踪它们,因此,如果你被允许用某个可能在托管堆中的值类型的地址,那么除了本机指针外,还需要一个追踪形态的指针。

到底该怎样去权衡呢?一方面,需要考虑简洁和安全。直接引入对一个或一组追踪指针的支持会使语言变得更复杂。如果不提供这种支持,由于所需的复杂程度降低,从而可以找到的程序员人群就会增加。此外,允许程序员访问这些生命期短暂的值类型,则增加了程序员出错的可能性。她经意或不经意地对内存做一些危险动作。不支持追踪指针,可以潜在地创建较安全的运行时环境。

另一方面,必须考虑效率和灵活性。每次将值类型赋值给相同的对象,该值都会发生新的框入/框出操作。允许访问这种经过框入/框出操作的值类型 ,就允许在内存中进行更新操作,这样便可能提供重要的性能改进。没有某种形式的追踪指针,你将无法用指针算法遍历CLI数组,这意味着CLI数组将不能 融入STL(标准模板库)中的迭代器模式,也无法与泛型算法协同工作。允许访问框入/框出值类型将会大大提高设计的灵活性。

在C++/CLI 中,微软选择提供一系列在托管堆中处理值类型的寻址模式:

int ival = 1024;
int^ boxedi = ival;

array<int>^ ia = gcnew array<int>{1,1,2,3,5,8};
interior_ptr<int> begin = &ia[0];

value struct smallInt { int m_ival; … } si;
pin_ptr<int> ppi = &si.m_ival;

典型的 C++/CLI 开发人员是一个经验丰富的系统程序员,其任务是提供底层架构以及作为基础的核心应用,以此为基础来构建未来。她必须解决可伸缩性和性能相关的问题,并且必须从系统一级来看待底层 CLI。某种 CLI 语言的细节标准反映了其程序员的面貌。

复杂性本身并不是对质量的否定,人类生命比单核细胞复杂得多,这当然不是一件坏事,然而,当单一概念的表达变得复杂化以后,这常常被认为是一件坏事。在C++/CLI中,CLI开发团队已经 尝试提供一种优雅的方式来表达一个复杂的主体。

附加功能

第三个设计层面是特定语言层功能要超过被CLI直接支持的功能,这样就需要建立一种语言层支持与CLI底层实现模型之间的映射。 在某些情况下,这是做不到的,因为该语言无法调解CLI的行为,在基类的构造函数和析构函数中解决虚函数便是例子。为了在这种情况中反映ISO-C++语义,需要在每个基类的构造函数和析构函数中 重新安排虚表。这是不可能的,因为虚表操作是由运行时托管的,而非单独的语言托管。 因此,这一设计层面是优越性和可行性的折中。C++/CLI 提供的附加功能主要有三个方面:

引用类型的资源获取(Resource Acquisition)形式是Initialization(RAII), 尤其是为被称作占据稀有资源的垃圾回收类型确定性终止化(deterministic finalization)提供一个自动化的机制;

与C++拷贝构造函数和拷贝赋值操作符相关的深度拷贝语义形式,但它不能扩展到值类型;

除了 CLI泛型机制之外——这原来是我第一个专栏的主题,还为CTS类型提供C++模板的直接支持,另外,还提供用于 CLI 类型的 STL 可验证版本;

让我们看一个简单的例子:确定性终止化问题。与对象关联的内存被垃圾回收器回收之前,若存在与之相关连的 Finalize 方法,该方法将会被调用。你可以把 该方法看作是一种超级析构函数,因为它不依赖于该对象程序的生命期,它被称为终止化。调用 Finalize 方法的时间,甚至是否调用它是未定义的。这就是垃圾回收器不确定的终止化操作含义之所在。

不确定性终止化在进行动态内存管理时可以很有效地工作,当可用内存空间严重不足时,垃圾回收器会发挥作用并解决问题。但是当对象涉及的是某些重要资源,比如数据库连接、某种 类型的锁、本地堆内存时,