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

📄 天方夜谭vcl:开门.htm

📁 csdn10年中间经典帖子
💻 HTM
📖 第 1 页 / 共 2 页
字号:
	* really a void**. Be sure when using this method to provide
	* two levels of indirection and cast away one of them.
	*/

        bool GetInterface(const TGUID &IID, /* out */ void *Obj);

        /* class method */
        static PInterfaceEntry GetInterfaceEntry(const TGUID IID);
        static PInterfaceTable * GetInterfaceTable(void);

        ShortString ClassName()
        {
                return ClassName(ClassType());
        }

        bool ClassNameIs(const AnsiString string)
        {
                return ClassNameIs(ClassType(), string);
        }

        TClass ClassParent()
        {
                return ClassParent(ClassType());
        }

        void * ClassInfo()
        {
                return ClassInfo(ClassType());
        }

        long InstanceSize()
        {
                return InstanceSize(ClassType());
        }

        bool InheritsFrom(TClass aClass)
        {
                return InheritsFrom(ClassType(), aClass);
        }

        void * MethodAddress(const ShortString &Name)
        {
                return MethodAddress(ClassType(), Name);
        }

        ShortString MethodName(void *Address)
        {
                return MethodName(ClassType(), Address);
        }

        virtual HResult SafeCallException(TObject *, void *);
        virtual void AfterConstruction();
        virtual void BeforeDestruction();
        virtual void Dispatch(void *Message);
        virtual void DefaultHandler(void* Message);

private:
        virtual TObject* NewInstance(TClass cls);

public:
        virtual void FreeInstance();
        virtual ~FObject();  /* Body provided by VCL {} */
};
</PRE>当然FObject::ClassType我们已经会写了,那就是 <PRE>TClass FObject::ClassType()
{
        return *(TClass*)this;
}
</PRE>我们会在后面陆续把这些成员函数填充完整。先举个例,拿类名(ClassName)开刀吧。 
<P>查查VMT表,vmtClassName = 0xffffffd4,我们就从这里下手。主要的步骤是: 
<UL>
  <LI>找到VMT的入口; 
  <LI>通过vmtClassName找到储存类名的地址; 
  <LI>获取类名。 </LI></UL>
<P>0xffffffd4也就相当于– 
44,也就是VMT入口指向的地址开始,倒数第44字节到倒数第41字节这4个字节所代表的指针,指向类名。假设入口指向的地址是cls,那么vmtClassName所代表的地址就是(char*)cls 
– 44,亦即(char*)cls + vmtClassName。 
<P>注意一个字符串格式的问题,VCL既然是用Object 
Pascal写的,其中储存类名的字符串的格式必然是Pascal传统方式,也就是第1个字节为字符串的长度,紧接着为字符串的实际内容。在C++ 
Builder中,与之对应的类型是ShortString。 
<P align=center><IMG 
src="天方夜谭VCL:开门.files/vcl05.gif"><BR><BR>图5 TObject::ClassName的运作方式 
<P>代码如下: <PRE>ShortString FObject::ClassName(TClass cls)
{
        ShortString* r = *(ShortString**)((char*)cls + vmtClassName);
        return *r;
}
</PRE>我们不妨测试一下。 <PRE>#include <VCL.H>
#include <MEMORY>
#include <IOSTREAM>
using namespace std;
... 插入FObject相应的代码... 
void main()
{
        auto_ptr<TLIST> list(new TList);
        FObject* p = (FObject*)list.get();
        cout &lt;&lt; AnsiString(p-&gt;ClassName()).c_str() &lt;&lt; endl;
        cout &lt;&lt; AnsiString(list-&gt;ClassName()).c_str() &lt;&lt; endl;
}
</PRE>输出结果在我们意料之内,都是“TList”。 
<P>对于函数ClassNameIs,我们就可以轻而易举地完成了。 <PRE>bool FObject::ClassNameIs(TClass cls, const AnsiString string)
{
        return string==ClassName(cls);
}
</PRE>有朋友可能奇怪,你怎么知道TObject::ClassName是这样的呢? 
<P>三种办法:
<UL>
  <LI>猜,用经验推测; 
  <LI>看Borland提供的原始码; 
  <LI>看编译以后的汇编码。 </LI></UL>
<P>在Borland提供的原始码中,我们可以看到TObject::ClassName的实现如下: <PRE>class function TObject.ClassName: ShortString;
asm
        { -&gt;	EAX VMT							}
        {		EDX Pointer to result string	}
        PUSH    ESI
        PUSH    EDI
        MOV     EDI,EDX
        MOV     ESI,[EAX].vmtClassName
        XOR     ECX,ECX
        MOV     CL,[ESI]
        INC     ECX
        REP     MOVSB
        POP     EDI
        POP     ESI
end;
</PRE>熟悉汇编的朋友就可以由此写出相应的C/C++代码来。对于不会的朋友,根据我们的讲解,相信也可以轻而易举地完成吧。 
<P>希望您在看这段的时候,不妨先用第1种办法,然后结合2、3看看,一定收获不小。 
<H5>势如破竹</H5>
<P>接下来就太简单了,我们不再举例,把相应的成员函数补充完整即可。您不妨先自己试着写写,探索一下,再与汇编代码和文中的代码作比较,一定乐趣无穷。 
<P>TObject::ClassInfo是做什么的?问我啊?我也不知道。VCL的帮助里说,用ClassInfo可以访问包含对象类型、祖先类和所有published属性信息的RTTI表。这个表只是内部使用,TObject提供了其它方法来访问RTTI信息。我们先写出它的实现。 

<P align=center><IMG 
src="天方夜谭VCL:开门.files/vcl06.gif"><BR><BR>图6 TObject::ClassInfo的运作方式 <PRE>void * FObject::ClassInfo(TClass cls)
{
        return *(void**)((char*)cls + vmtTypeInfo);
}
</PRE>Borland的说法可信吗?这个函数返回值的类型是void 
*,明摆着不愿意透露更多的信息。您不妨按上面ClassName的方法测试一下,对于TList,ClassInfo输出的结果居然是0!也就是一个空指针!什么东东?别急,后面我们会掀开这个void 
*的面纱,现在姑且卖个关子。 
<P>VCL框架中只存在单继承,这是由Object 
Pascal语言的特性决定的。这样,每一个类只有唯一一个父类,函数TObject::ClassParent就能帮您把父类找出来。 <PRE>TClass FObject::ClassParent(TClass cls)
{
        TClass* r = *(TClass**)((char*)cls + vmtParent);
        return (r)? (*r) : 0;
}
</PRE>由此,我们也能很轻松地模拟TObject::InheritsForm的实现。 <PRE>bool FObject::InheritsFrom(TClass cls, TClass aClass)
{
        while(aClass)
        {
                if(aClass==cls)return true;
                cls = ClassParent(cls);
        }
        return false;
}
</PRE>要知道一个对象所占的字节数,TObject::InstanceSize就可以达到目的。 <PRE>long FObject::InstanceSize(TClass cls)
{
        return *(long*)((char*)cls + vmtInstanceSize);
}
</PRE>有朋友可能说,C++不是有sizeof操作符吗?为什么不用呢?在VCL中,sizeof有两个缺陷。首先sizeof是完全静态的,也就是说,如果您写sizeof(...),编译以后,这会被替换为一个常数,没有任何的求值过程,因此不能动态求值;其次,VCL类必须与指针或引用的形式存在。所以对于 
<PRE>TObject *a;
...
</PRE>sizeof(*a)这样的表达式是错误的。而且即使TObject不是VCL类,使用sizeof(*a)还是相当于sizeof(TObject),没有实际价值。 

<H5>结束语</H5>
<P>现在我们已经打开了通向VCL类秘密的大门。回头一看,VMT跟VFT有什么区别与联系呢?其实VMT可以算是VFT具体化的一个结果,也就是说,VMT是在VFT基础上发展出来的一种具有“规范”性质的结构,所有的VCL类都遵循这个“规范”。这很像COM与C++纯虚基类的关系。 

<P>通过VMT,VCL放置了一些重要的信息,由此来实现RTTI。所以“高级”RTTI功能其实是相当低级和简单的一项技术。就其实现方式而言,大致有三种。MFC用宏(macro)模拟算是一类,完全符合C++标准,不需要对语言进行扩充,也不依赖于特定的编译器,不过给人臃肿的感觉;VCL则是完全由编译器实现,同时扩充了C++的语言特性,必须在Borland的编译器上编译,但是很简洁;另外建构KDE基础的跨平台框架Qt[<A 
href="http://www.c-view.org/journal/003/vcl_chong.htm#44" 
name=4>4</A>],则采用了折中的方式,扩充了C++的关键字,书写很简洁,在编译之前必须用Qt提供的程序MOC进行预处理,把扩充部分的代码改写为符合C++标准的代码,然后才可以在任何符合C++标准的编译器上编译。 
<BR><BR>
<TABLE align=center border=1 borderColor=#111111 cellPadding=2 
style="BORDER-COLLAPSE: collapse">
  <THEAD bgColor=#66ccff>
  <TR>
    <TD>代表作</TD>
    <TD align=middle>实现方式</TD>
    <TD>不依赖特定编译器</TD>
    <TD>简洁程度</TD>
    <TD align=middle>编译次数</TD></TR>
  <TBODY>
  <TR>
    <TD align=middle>MFC</TD>
    <TD align=middle>宏插入</TD>
    <TD align=middle>是</TD>
    <TD align=middle>一般</TD>
    <TD align=middle>1</TD></TR>
  <TR>
    <TD align=middle>VCL</TD>
    <TD align=middle>编译器生成</TD>
    <TD align=middle>否</TD>
    <TD align=middle>好</TD>
    <TD align=middle>1</TD></TR>
  <TR>
    <TD align=middle>Qt</TD>
    <TD align=middle>预编译程序生成</TD>
    <TD align=middle>是</TD>
    <TD align=middle>较好</TD>
    <TD align=middle>2(包括MOC)</TD></TR></TBODY></TABLE>
<P>注:如果长期仅在Windows平台下进行开发的朋友,可能没有听说过Qt的大名。事实上在Linux世界里,这可是个响当当的名头。Qt是一套完善的C++框架,横跨Unix/Linux、Windows、Mac 
OS诸多平台,内部机制相当有趣。Borland最新的Kylix和Delphi6所采用的跨平台框架CLX(分为BaseCLX、VisualCLX、DataCLX、NetCLX四个部分,BaseCLX与VCL顶部几个类相同),其可视化部分VisualCLX就建构在Qt上,这多少让我感到失望和不满。Qt本身就跨平台,VisualCLX建构在Qt上,自然也跨平台;但是CLX是用Object 
Pascal包装了一个C++框架,我不敢想象,C++ Builder6中的CLX是否又用C++再来包装这个包装了C++框架的Object 
Pascal框架呢?如果真是如此,其效率和调试难度…… 
<P>对于“高级”RTTI的实现形式,VCL用了TMetaClass(其中TClass就是TMetaClass*)来配合存储类的信息,也就是所谓“类的类”,这非常普遍。MFC中的CObject与CRuntimeClass,JDK中的java.lang.Object和java.lang.Class都是如此。比如对于一个TObject 
*p,如何获取其父类名呢?我们必须借助TMetaClass:可以先用p-&gt;ClassType返回父类的信息(是一个TMetaClass*类型),再以此为参数传入TObject::ClassName就可以获得结果,也就是TObject::ClassName(p-&gt;ClassType())即可。 

<P>同时我们也应该拆穿所谓“拥有更高级RTTI的语言本身也更高级”的谎言。至少从我那少得可怜的经验来看,对于一套框架,除非需要和IDE配合,否则在绝大部分情况下,RTTI是完全没有必要的,甚至是有害的[<A 
href="http://www.c-view.org/journal/003/vcl_chong.htm#55" 
name=5>5</A>]。希望使用和设计框架的朋友三思。 
<H5>致谢</H5>
<P>非常感谢孟岩和孙春阳对本文所提出的宝贵意见。 
<H5>参考</H5>
<P><A href="http://www.c-view.org/journal/003/vcl_chong.htm#1" name=11>1</A>. 
Bjarne Stroustrup. The C++ Programming Language, 3e. Addison-Wesley, Reading, 
MA. 1997. 
<P><A href="http://www.c-view.org/journal/003/vcl_chong.htm#2" name=22>2</A>. 
Stanley Lippman. Inside the C++ Object Model. Addison-Wesley, Reading, MA. 1996 
<BR>&nbsp;&nbsp;&nbsp;&nbsp;侯捷.《深度探索C++物件模型》.碁峰资讯股份有限公司.1998. 
<BR>&nbsp;&nbsp;&nbsp;&nbsp;侯捷.《深度探索C++对象模型》.华中科技大学出版社.2001. 
<P><A href="http://www.c-view.org/journal/003/vcl_chong.htm#3" name=33>3</A>. 
侯捷.《深入浅出MFC》,2e.松岗电脑图资料股份有限公司/华中科技大学出版社.1997/2001. 
<P><A href="http://www.c-view.org/journal/003/vcl_chong.htm#4" name=44>4</A>. 
虫虫.《Qt最新消息》.C++ View.2001,7. 
<P><A href="http://www.c-view.org/journal/003/vcl_chong.htm#5" name=55>5</A>. 
Robert C.Martin. “The Open-Closed Principle”. C++ Report. 1996, 1. 
<BR>&nbsp;&nbsp;&nbsp;&nbsp;plpliuly,虫虫.《开放封闭原则OCP》.C++ View.2001,8. 
</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 + -