⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 天方夜谭vcl:生死.htm

📁 csdn10年中间经典帖子
💻 HTM
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0047)http://www.c-view.org/journal/006/vcl_chong.htm -->
<HTML><HEAD><TITLE>天方夜谭VCL:生死</TITLE>
<META content="text/html; charset=gb2312" http-equiv=Content-Type><LINK 
href="天方夜谭VCL:生死.files/style.css" rel=stylesheet type=text/css>
<META content="MSHTML 5.00.3315.2870" name=GENERATOR></HEAD>
<BODY>
<SCRIPT src="天方夜谭VCL:生死.files/header.js"></SCRIPT>

<CENTER>
<H3><A href="http://www.c-view.org/tech/framework/vcl_chong.htm">天方夜谭VCL</A>: 
生死</H3>虫虫<BR></CENTER>
<P>生命是什么?科学和宗教都给出了不同的诠释。有句话也许说得更有意思:生命是这样一种东西,如果你把它当作一个开场或结局,那么它总是一样的;而当你把它当作一个过程,它总是不同的。其实,万事万物又何尝不是分别以生和死作为开场和结局呢?对象也不例外,不过生成以及销毁对象都需要健全的机制作保证。否则不仅对象本身遭殃,甚至会导致程序乃至整个系统崩溃。 

<P>传说中,东方的天、人、阿修罗、畜生、饿鬼、地狱六道轮回(以及由此演变出的丰都鬼城),和西方的地狱、炼狱、天堂,都有一套非常完整、严密、健全的机制,管理着时空中各种生命体。同样,一套框架也需要这么一套机制来管理记忆体中的对象,以保证正常运作。VCL自然也不例外。但虫虫这次并不准备详细分析涉及VCL对象生死的代码,相信大家对剖析涉及底层汇编都有了一定的经验。所以前面虫虫会对这方面提几句,把重点放在设计的结构和模式上,并解决一个在BBS上看到的问题。 

<H5>对象生成</H5>
<TABLE>
  <TBODY>
  <TR>
    <TD>
      <P>对象生成的方式几乎都是一样,一般流程如右图所示(VCL类的初始化是指初始化VMT和接口指针)。对象一般生存在两个地方,栈(stack)或自由存储区(free 
      store)[<A href="http://www.c-view.org/journal/006/vcl_chong.htm#11" 
      name=1>1</A>]中。由于Object 
      Pascal只支持第二种方式,所以VCL类都在自由存储区中,表现在C++中就是必须使用new和delete分配、回收空间,速度自然会比存在于栈中的普通C++类要慢一些。 

      <P>控制VCL对象生成过程的代码主要在TObject::InstanceSize、TObject::NewInstance、TObject::InitInstance几个成员函数中。有兴趣的朋友可对照右图分析一下,源代码在Source/Vcl/system.pas里,没有什么特别之处。我们将把重点主要放在分析动态生成(Dynamic 
      Creation)机制上。 
      <P>动态生成是一个相当实用的技术。比如上次我们提到的一个绘制图形的程序[<A 
      href="http://www.c-view.org/journal/006/vcl_chong.htm#22" 
      name=2>2</A>],具体的图形以插件的方式提供,主程序对相应的图形类一无所知,但是仍然需要“动态”地生成这些对象。又比如Delphi/C++ 
      Builder的IDE对象设计器,也是一个很好的例子:鼠标双击,一个对象就动态生成在设计面板,可以供我们设计之用了。C++语言本身并没有也不可能提供对动态生成的支持,不过MFC中用宏(macro)模拟就可以取得令人满意的效果[<A 
      href="http://www.c-view.org/journal/006/vcl_chong.htm#33" name=3>3</A>]。 
      </P></TD>
    <TD width=10></TD>
    <TD align=middle noWrap><IMG 
    src="天方夜谭VCL:生死.files/vcl01.gif"><BR>图1 对象生成流程</TD></TR></TBODY></TABLE>
<P>仔细想来,动态生成是个很好笑的技术,它需要程序生成一个对其性质并不清楚的对象。您能造一个您不知道的东西吗?不可能。但是如果告诉您制造的原料和方法呢?那当然就很简单了。所以动态生成的关键是:留好事先约定的接口。MFC的宏模拟就是一种方式,Object 
Pascal则是使用了另一种方式。 
<P>“高级”RTTI方式往往会引入一个所谓“类的类”,即“元类(metaclass)”的概念[<A 
href="http://www.c-view.org/journal/006/vcl_chong.htm#44" name=4>4</A>]。在Object 
Pascal中,每个类都有一个代表其相应信息的类。代表TObject类信息的类是TClass,代表TPersistent类信息的类就是TPersistentClass…… 

<P align=center><IMG src="天方夜谭VCL:生死.files/vcl02.gif"><BR>图2 实现RTTI的metaclass 
<P>Object Pascal可以借此来实现对TComponent派生类的“动态生成(Dynamic Creation)”机制。 <PRE>function CreateComponent(AOwner: TComponent;AClass: TComponentClass)
:TComponent;
var
  Instance: TComponent;
begin
  Instance := TComponent(AClass.NewInstance);
  {try}
    Instance.Create(Owner);
{except
     raise;
   end;}
  CreateComponent := Instance;
end;
</PRE>但是在C++ Builder中,这一切就无效了。对于所谓的“元类”,C++ 
Builder的VCL中只提供TMetaClass类,并且功能很少,甚至根本没有提供NewInstance等方法,巧妇难为无米之炊啊。Borland怎么这么偏心眼呢?又怎么办呢?国内各大BBS上,我问过,也看别人问过这样的问题,可以总是没有答案。在国外的BBS上这样的问题也不少,而常见的解决方案(是,用Object 
Pascal写单元,再让C++调用,这也未免…… 
<H5>动态生成的难点</H5>
<P>TMetaClass究竟是什么?我们已经知道,就是指向VMT入口的指针。假如给我们一个TMetaClass,我们能做到动态生成吗?对照对象生成的流程图,我们来分析 

<UL>
  <LI>获取类实体大小:通过TMetaClass::InstanceSize可以做到; 
  <LI>分配空间:这个当然可以; 
  <LI>初始化:如果无特殊需要,直接调用TObject::NewInstance就行了。老弟,不是开玩笑吧?TObject::NewInstance是private!呵呵,想个办法绕过去,通过VMT表“开门”不就OK?) 

  <LI>调用构造函数:倒!这个怎么办?TMetaClass里可没记录过某个类的构造函数,再说,一个类的构造函数好象也不只一个吧? </LI></UL>
<P>唯一的问题,就出在构造函数上:我们需要一个形式固定的“虚”构造函数,也就是我们刚才说的:留好实现约定的接口。这一招的要点,跟MFC是一致的。 
<P>虚构造函数?C++的构造函数可以是虚(virtual)的吗?当然不可以。VCL类从TComponent类开始,构造函数就是虚的(难怪刚才我们只生成TComponent的派生类),Object 
Pascal所谓的虚构造是如何做到的呢?Scott Meyers在Virtualizing constructors and non-member 
functions一文[<A href="http://www.c-view.org/journal/006/vcl_chong.htm#55" 
name=5>5</A>]中详细说明了所谓虚构造的实现方式:事先约定一个普通的虚函数,其功能是构造函数而已。也就是说,Object 
Pascal所谓的虚构造函数(名字是Create),不过是一个事先约定好功能的普通虚成员函数罢了。MFC是这么做的,事实上,VCL也一定差不多。在设计模式中,这叫做FACTORY 
METHOD模式,正好又名VIRTUAL CONSTRUCTOR模式[<A 
href="http://www.c-view.org/journal/006/vcl_chong.htm#66" name=6>6</A>]。 
<P>那么,这个虚函数一定可以在VMT中找出来!问题不就解决了吗?在TComponent类的VMT入口开始的若干个指针地址中找出TComponent::TComponent(也就是Object 
Pascal中的TComponent.Create),相信是很容易的事情吧? <PRE>#include <VCL.H>
#include <IOSTREAM>
using namespace std;

void main()
{
        void** p = (void**)__classid(TComponent);
        for(int i = 0; i &lt; 15; ++i)
                cout &lt;&lt; *(p++) &lt;&lt; '\t';
}
</PRE>
<P>这几行程序能输出TComponent的VMT中前15个函数地址。在我的机器上输出结果是(也许在您那里有所不同): <PRE>40026BD4		40030A54		40026AF0		40030B2C		400309F8
40030B38		40030C30		40030F88		40030B48		40030B40
40030F90		<B><I>400306CC</I></B>		0000000E		00010000		45840000
</PRE>再查查TComponent::TComponent的地址。IDE菜单View-&gt;Debug 
Windows-&gt;Modules,选择vcl50.bpl,找到TComponent::TComponent(如下图)。看到了吧?它的地址跟上面输出的第12个地址完全相同,这,就是我们的目标!(其实我们还有更偷懒的方法:看看前面那段Object 
Pascal程序的汇编代码就行啦!) 
<P align=center><IMG 
src="天方夜谭VCL:生死.files/vcl03.gif"><BR>图3 寻找TComponent::TComponent的地址 
<P>现在写个我们自己的CreateComponent很容易了吧?从VMT入口算,第12个指针是构造函数的指针。而VCL类是“虚”构造的,所有TComponent的派生类的构造函数地址也会放在那里。所以,从VMT第一个指针往前数11个,就是我们需要的第12个指针了。 
<PRE>TComponent* CreateComponent(TComponent* AOwner, TClass cls)
{
        TComponent* r;
        const void* const fn = *(void**)((char*)cls + vmtNewInstance),
        		       * const fc = *((void**)cls + 11);
		//fn是NewInstance的地址,
		//fc则是构造函数的地址,从第一个往后数11个,即第12个

	//下面为了偷懒,用几句汇编
	asm{
		mov eax, cls
		call fn			//调用NewInstance,执行图1流程前3步
		mov r, eax
		mov edx, AOwner
		call fc			//调用构造函数
	}
	return r;
}
</PRE>我们不妨测试一下。 <PRE>#pragma inline
#include <VCL.H>
#include <IOSTREAM>
using namespace std;

class TTestButton: public TButton
{
public:
        __fastcall TTestButton(TComponent* Owner): TButton(Owner)
        {
                cout &lt;&lt; "Hello!" &lt;&lt; endl;
        }
        __fastcall ~TTestButton()
        {
                cout &lt;&lt; "Goodbye!" &lt;&lt; endl;
        }
};

TComponent* CreateComponent(TComponent* AOwner, TClass cls)
{
       //...
}

void main()
{
        TComponent* p = CreateComponent(0, __classid(TTestButton));
        cout &lt;&lt; AnsiString(p-&gt;ClassName()).c_str() &lt;&lt; endl;
        delete p;
}
</PRE>输出: <PRE>Hello!
TTestButton
Goodbye!
</PRE>结果令人满意! 
<H5>属主机制</H5>
<P>这里简单提一下VCL类的组织形式:属主(Owner)机制。 
<P>从TComponent开始,VCL类的构造函数就带有一个TComponent*类型(Object 
Pascal中表现为TComponent)的参数AOwner。每个从TComponent继承的类的实体都拥有唯一一个所有者(Owner,亦即属主),这就决定了这些类之间是树形关系。例如 
<PRE>TForm* Form1 = new TForm(Application);
TForm* Form2 = new TForm(Application);
TButton* Button1 = new TButton(TForm1);
</PRE>
<P align=center><IMG src="天方夜谭VCL:生死.files/vcl04.gif"><BR>图4 树形结构 
<P>这样就形成了如下以Application为根(Root)的树形结构: 
<P>上图中的箭头表示从属关系,也就是下图的意思 
<P align=center><IMG src="天方夜谭VCL:生死.files/vcl05.gif"><BR>图5  从属关系 
<P>于是当Application析构的时候,它会通知自己所拥有的对象Form1和Form2析构,Form1和Form2再通知自身所拥有的对象析构,如此递归下去,正如同推倒了多米诺骨牌(Domino):Form1和Form2以及它们所拥有的对象,不用显式调用析构函数,资源就自动回收了。 

<P>这种Owner机制就是设计模式中典型的结构型模式COMPOSITE(组合)[<A 
href="http://www.c-view.org/journal/006/vcl_chong.htm#66" 
name=6>6</A>],即某个对象拥有一系列类似的对象,而这一系列对象中的每一个又拥有一系列的对象,如此递归下去,管理起来很方便。由Application向其所拥有的对象发送析构的消息的方式,是典型的行为模式Observer(观察者),这也是VCL消息处理的基本模型,我们以后会详细讨论这一问题。 

<H5>小结</H5>
<P>本来开始想对对于VCL类实体具体的生成和销毁做汇编级的分析,后来发现没有多少新的东西,就留给有兴趣的朋友钻研吧。我不得不承认,这篇文章的“黑客”气太重了点儿,扩充性并不好。下次我们将逐步进入VCL消息处理机制,看看它的精华部分。 

<H5>参考</H5>
<P><A href="http://www.c-view.org/journal/006/vcl_chong.htm#1" name=11>1</A>. 
Herb Sutter. Exceptional C++. Addison-Wesley, Reading, MA. 1999. 
<BR>&nbsp;&nbsp;&nbsp;&nbsp;侯捷.《Exceptional C++中文版》.培生教育出版集团.2000. 
<P><A href="http://www.c-view.org/journal/006/vcl_chong.htm#2" name=22>2</A>. 
虫虫.《<A 
href="http://www.c-view.org/journal/004/vcl_chong.htm">天方夜谭VCL:多态</A>》.C++ 
View.2001,10. 
<P><A href="http://www.c-view.org/journal/006/vcl_chong.htm#3" name=33>3</A>. 
侯捷.《深入浅出MFC》,2/e.松岗电脑图资料股份有限公司/华中科技大学出版社.1997/2001. 
<P><A href="http://www.c-view.org/journal/006/vcl_chong.htm#4" name=44>4</A>. 
虫虫.《<A 
href="http://www.c-view.org/journal/003/vcl_chong.htm">天方夜谭VCL:开门</A>》.C++ 
View.2001,9. 
<P><A href="http://www.c-view.org/journal/006/vcl_chong.htm#5" name=55>5</A>. 
Scott Meyers. More Effective C++. Addison-Wesley, Reading, MA. 1996. 
<BR>&nbsp;&nbsp;&nbsp;&nbsp;侯捷.《More Effective C++中文版》.培生教育出版集团.2000. 
<P><A href="http://www.c-view.org/journal/006/vcl_chong.htm#6" name=66>6</A>. 
GoF. Design Patterns: Elements of Resuable Object-Oriented Software. 
Addison-Wesley, Reading, MA. 1995. 
<BR>&nbsp;&nbsp;&nbsp;&nbsp;李英军等.《设计模式:可复用面向对象软件软件的基础》.机械工业出版社.2000. 
</TABLE><BR>
<HR width=700>
<BR>
<TABLE align=center border=0 height=17 width=360>
  <TBODY>
  <TR align=middle>
    <TD>&copy;2000-2002 C-View.ORG All Rights 
Reserved.</TD></TR></TBODY></TABLE><BR></BODY></HTML>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -