在《Inside VCL》还未出版之前,我从网上 Down 了前两章看了看,发现书的第二章所讲的一些内容可能存在一些 Bug。不过,由于当时书没有正式出版,我也就没有深究。昨天下午逛书店,看到书架上的《Inside VCL》,忍不住还是把它买下来了。但是回家后一看发现,我原来认为有问题的地方还是老样子。 加上近段时间,CSDN Delphi 版中也有一些关于该书中某些问题的讨论贴,所以,我就把自己的一些看法写下来,请各位来讨论讨论。
当然,由于小弟的水平所限,有些"问题"可能是我自己的错,请大家海涵!
问题一:书中的论述是这样的:
{ ======================================================== }
让我们看一个范例,在下面的程序代码中声明了如下的TBase类:
TBase = class(TObject)
private
  { Private declarations }
  iRef : Integer;
  ...
end;
要建立TBase对象时除了可使用下面的建构函数:
aBObj := TBase.Create;
我们也可以使用如下的程序代码:
var
  aBObj : TBase;
  aObj : TObject;
begin
  ...
  try
    aObj := TObject(TBase.NewInstance);
    aBObj := TBase(aObj.Create);
    // 上面一句可能存在问题。
    // 按照这种写法,调用的是 TObject.Create 而不是 TBase.Create。
    // 那么这种写法和 aBObj := TBase.Create; 是不等价的。
    // 
    // 举个例子,假设 TBase.Create 函数体内有一条赋值语句 iRef := 100;
    // 当我们使用通常的对象创建方法,即 aBObj := TBase.Create;
    // 此时,当对象构造完毕时 aBObj.iRef 是等于 100 的。
    // 但如果按照现在的写法,aBObj.iRef 仍然为 0。
    // 但如果这个语句改成 aBObj := TBase(aObj).Create; 就不会有问题。
    //
    // 李维在这里举了一个 TApplication.CreateForm 的例子来验证他的说法。
    // 但有一点他没有注意到 TComponent 的构造函数 Create 是 virtual 的。
    // 而 TObject.Create 却不是 virtual 的。
    ...
  finally
    FreeAndNil(aBObj);
  end;
end;
{ ======================================================== }
问题二:书中的论述是这样的:
{ ======================================================== }
因此我们可以推知 MethdodName 似乎只会对从 TComponent 继承下来的类的 published 方法才有作用。
//
// 李维得出这个结论是来源于下面的这个例子
// 
procedure TForm1.Button7Click(Sender: TObject);
var
  aObj : TDerived;
  sData : String;
  aPtr : Pointer;
begin
  sData := 'TDerived at ' + DateTimeToStr(Now);
  aObj := TDerived.Create(sData, HashOf(sData));1
  ...
  ShowMethodAddress(aObj, 'MyMethod1');
  aPtr := ShowMethodAddress(aObj, 'MyMethod2');
  sData := MethodName(aPtr);
  // 由于此处运行的结果是 sData = ''; 也就是没能获得相应的 MethodName
  // 因此,李维得出了上述结论。
  // 但是我认为这个结论是错的。因为这个语句有问题。
  // 
  // sData := MethodName(aPtr); 实际上调用的是 TForm1.MethodName 函数
  // MyMethod2 根本不是 TForm1 的成员函数,当然获取不了 MethodName。
  // 
  // 如果该语句改成 sData := TDerived.MethodName(aPtr); 
  // 或者改成 sData := aObj.MethodName(aPtr); 
  // 都能够获得正确的 MethodName。
  //
  // 因此,MethdodName 对从 TObject 继承下来的类的published方法都有作用。
  // 李维的结论是不对的。
  
  sData := Format('%x : %s', [Integer(aPtr), sData]);
  Memo1.Lines.Add(sData);
  ...
end;
{ ======================================================== }
问题三:书中的论述是这样的:
{ ======================================================== }
现在让进行一个实验,看看VMT表格建立的时机。
procedure TForm1.Button4Click(Sender: TObject);
var
  aPnl : TPanel;
begin
  aClass := aPnl.ClassType;
  sClassName := 'TPanel';
  ShowVMTContent(aClass);
  aPnl := TPanel.Create(Self);
  aClass := aPnl.ClassType;
  sClassName := 'TPanel';
  ShowVMTContent(aClass);
  FreeAndNil(aPnl);
end;
上面的程序代码首先声明了一个区域 TPanel 变量 aPnl,接着在还未实际建立 TPanel 对象之前我们藉由 aPnl 显示 VMT 的内容,得到的结果应该是无意义的内容。然而一旦当 TPanel 对象实际在内存中建立之后就可以取得 TPanel 的 VMT 中的内容。我们应该可以推知 VMT 应该是在第一个类对象被建立时才会建立完整的 VMT 内容。// 我个人认为,李维的这个结论也是错误的。
// 因为当还未实际建立 TPanel 对象时,aPnl 并没有指向一个实际的 TPanel 对象,因此 aPnl 更不可能跟 TPanel 的 VMT 有关联。但是这并不能证明 TPanel 的 VMT 就没有建立。实际上,我们可以在没有建立任何 TPanel 对象时,调用 TPanel.ClassName,而且该函数能够返回正确的值 'TPanel',而 ClassName 就是从 VMT 中获得的。我想这也能够说明,在第一个类对象被建立时,VMT 已经建立起来了。
{ ======================================================== }

解决方案 »

  1.   

    HOHO?大版也认为是正确的?个人认为第一个不是错误,那里仅仅是认为结果相同,过程并不相同,仅仅是个表达疏忽我看Inside VCL是直接从第三章开始看的,所以前面的这些内容还没有注意到,不过刚才看了流铭兄的帖子后翻到了对应的地方看了看感觉第一个错误有点牵强,其他的还没有看....
      

  2.   

    李维的这本书虽然着重点在于系统架构的原因和面向事件的编程方法在Framework中的应用上——直接对应Design Patterns的应用,但书中一些细节的解释方式也的确让人有点感觉不塌实....而且印刷错误多多,实在是让人感到有点失望.....
      

  3.   

    刚才把原来的老帖子看了看,发现 xzgyb (老达摩) 兄在 2004-1-12 发过一个帖子(http://expert.csdn.net/Expert/topic/2658/2658508.xml?temp=.2076685)讲的就是我这个帖子里提到的问题。在老达摩兄的帖子里 lxpbuaa(桂枝香在故国晚秋)兄把这些问题讲得很清楚了。 我当时没有细看,只做了个记号,惭愧惭愧!
      

  4.   

    怎么大家都喜欢挑人家的刺儿,人家赚点黑心钱也不容易:)楼兄,你的说法也有Bug吧,     // 李维在这里举了一个 TApplication.CreateForm 的例子来验证他的说法。
        // 但有一点他没有注意到 TComponent 的构造函数 Create 是 virtual 的。
        // 而 TObject.Create 却不是 virtual 的。李大哥的书我没钱买,不过构造函数跟你虚不虚是没关系的,它实际是一个类方法(即使是通过实例的调用它也只相当于一个普通的方法而已),你把他的那段例子贴出来,咱哥们研究研究
      

  5.   

    To forgetter()兄:TApplication.CreateForm 在 Forms.pas 单元,详细代码如下:procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
    var
      Instance: TComponent;
    begin
      Instance := TComponent(InstanceClass.NewInstance);
      TComponent(Reference) := Instance;
      try
        Instance.Create(Self);
        // 如果 TComponent 的构造函数 Create 不是 virtual,
        // 那么上面一句只会执行 TComponent.Create。
        // 这样就会出现我所说的问题。
        //
        // 但是正因为 TComponent.Create 是 virtual,
        // 上面这一句会执行 InstanceClass 参数所表示的 TComponent 派生类的构造函数
        // 因此就不会出现问题。
        // 
        // 《Inside VCL》的前两章在网上有下载,forgetter()兄可以看看
        // 
      except
        TComponent(Reference) := nil;
        raise;
      end;
      if (FMainForm = nil) and (Instance is TForm) then
      begin
        TForm(Instance).HandleNeeded;
        FMainForm := TForm(Instance);
      end;
    end;
      

  6.   

    大家有问题可以直接发短消息问我,因为我直接参与了VCL的类库设计,只有我最清楚。
    解决一个问题50美刀,谢谢!
      

  7.   

    Instance.Create(Self);这句这里的Instance已经一个实例,而不是一个类了,Create已经一个override的一个由Instance调用的普通方法了,它已经不再是构造函数
      

  8.   

    To forgetter()兄:    这个地方 Create 的确没有执行“构造”功能,但是却有“初始化”的功能。因为 TComponent 派生类的 Create 函数体内一般还是会有一些代码(比如,对派生类对象的数据成员赋初值)的,权且称之“初始化代码”吧。    如果 TComponent.Create 不是 virtual,那么 Instance.Create(Self); 这句只会执行 TComponent 的“初始化代码”,这会导致 “TComponent 派生类”的“初始化代码”不能执行,最后就会出现诸如“派生类对象的数据成员仍然为 0”的不正确结果。    我在上面就举了一个例子:    // 举个例子,假设 TBase.Create 函数体内有一条赋值语句 iRef := 100;
        // 当我们使用通常的对象创建方法,即 aBObj := TBase.Create;
        // 此时,当对象构造完毕时 aBObj.iRef 是等于 100 的。
        // 但如果按照现在的写法,aBObj.iRef 仍然为 0。    // 这个地方我加一句, 
        // 如果“TBase 的基类”的 Create 是 virtual 的,
        // 而且 TBase.Create 进行了 override,那么就没什么问题了。    我不知道这样说 forgetter()兄能不能明白我的意思,但是兄可以做个简单的程序来测试一下。  我得出的结论就是我自己先前测试的结果。
      

  9.   

    何必搞得那么复杂    只要这样理解就够了,作为类方法(暂且这样说吧)来调用构造函数,它就作为一个切实的构造函数至于它怎么实现可以参考《悟透Delphi-DELPHI的原子世界》
        作为对象方法来调用你就把它当作普通的方法看待(实际当然不是这么简单),当然也就具有你说的多态特性
      

  10.   

    呵呵,forgetter() 兄说的是! 实际使用时,当然不会搞得这么麻烦。
      

  11.   

    Shit,流铭老弟,你怎么和这两个家伙称兄道弟了,完蛋了!
      

  12.   

    我觉得此书对于理解delphi结构还是很有帮助的,结合《delphi开发人员指南》来看还是不错的。
      

  13.   

    FS,好久没听你 GGYY 了! ^_^你是不是这段时间都忙着泡 PLMM ,什么时候跟我也介绍一下心得啊!
      

  14.   

    除了第一个,我也认为你的后两个的是对的。当时老达摩说的时候,我忙呢。嗬嗬。我倒着说:第三个,我曾经被DreamTheater大哥问,类所建立的VMT和对象的关系。当时我是一无所知……翻来覆去看帮助,才知道个所以然,只要使用这个类,VMT就建立了。不一定就经过构造函数。证据是当时抓得Debug CPU的图片,现在已经没有了。第二个,错误的太明显了。没有什么好说的………………
      

  15.   

    不过第一个不敢苟同,构造函数不仅仅是你说的那些。唉,怎么说呢,又不得不提一下DreamTheater大哥写的那篇文章,无论创建一个继承链有多长的类时,@ClassCreate和@AfterConstruction只会被调用一次。
    另外就是forgetter推荐的李战的《悟透Delphi-DELPHI的原子世界》另外我再推荐考鸡翅膀兄马洪喜的那篇文章:对Object Pascal编译器给类对象分配堆内存细节的一种大胆猜测http://www.csdn.net/develop/author/netauthor/mahongxi/
    你留下你的Email,我把大哥写的那篇文章发给你。嘿嘿,里面有一些是我写的呢。对了,FS,上次你不是说对我说的Object pascal先建立子类再建立父类不敢苟同吗?你也留下Email。我发给你。
      

  16.   

    晚上闲着没事,就研究了一下 VMT 建立的时机。后来的结论是: VMT 在程序编译的时候就已经确定,它是 PE 文件 CODE Section 的一部分。随着 PE 文件的加载,VMT 就能够被访问了。  以上纯属个人意见,仅供参考!  ^_^
      

  17.   

    我的 EMAIL:[email protected] REALLIKE
      

  18.   

    FS,什么意思 “Shit,流铭老弟,你怎么和这两个家伙称兄道弟了,完蛋了!”,小心我割了你的小JJ
      

  19.   

    是的,有些人就是喜欢刨根问底,但是,这些人一般都是高手。嘿嘿。马洪喜老兄就是,DreamTheater大哥也是,楼顶这位也是。嘿嘿。
      

  20.   

    TObject = class
        constructor Create;
        procedure Free;
        class function InitInstance(Instance: Pointer): TObject;
        procedure CleanupInstance;
        function ClassType: TClass;
        class function ClassName: ShortString;
        class function ClassNameIs(const Name: string): Boolean;
        class function ClassParent: TClass;
        class function ClassInfo: Pointer;
        class function InstanceSize: Longint;
        class function InheritsFrom(AClass: TClass): Boolean;
        class function MethodAddress(const Name: ShortString): Pointer;
        class function MethodName(Address: Pointer): ShortString;
        function FieldAddress(const Name: ShortString): Pointer;
        function GetInterface(const IID: TGUID; out Obj): Boolean;
        class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
        class function GetInterfaceTable: PInterfaceTable;
        function SafeCallException(ExceptObject: TObject;
          ExceptAddr: Pointer): HResult; virtual;
        procedure AfterConstruction; virtual;
        procedure BeforeDestruction; virtual;
        procedure Dispatch(var Message); virtual;
        procedure DefaultHandler(var Message); virtual;
        class function NewInstance: TObject; virtual;
        procedure FreeInstance; virtual;
        destructor Destroy; virtual;
      end;TObject的Create是“静态”(有的地方把这个词看做是类方法或者成员)方法。这样做是因为TObject.Create什么都没有做。
    constructor TObject.Create;
    begin
    end;如果它是virtual的话是要浪费很多内存的,要知道所有的类都是从它继承的!
    TObject的“初始化”是通过InitInstance,NewInstance等方法来实现的。但是李维这么说还是有点不妥。
      

  21.   

    对于问题二,有人问过李维,下面是李维的回答:>Page 2-29 最上兩行文字所說的觀念有錯,MethodName 不是似乎只會對從 TComponent 繼承下來的類別之 Published 方法才有作用
    >是,我已經知道這個問題,Inside VCL一些打字錯誤和這個問題會在再版中改在,不過我想簡體版應該會有再版,繁體版可能就沒有了.有關MethodName/MethidAddress的問題不一定是書的錯誤,而是服務定義和實作的不同.在根物件中定義的方法應該是基礎服務,特別是當MethodName/MethodAddreess被定義成class method時.依照class method的基本含意,所有Object Instance或是藉由class都可以呼叫, 因此MethodName/MethidAddress根據Contract應該是需要回傳正確數值的.然而對於由他物件來呼叫不會有結果在書中不會回傳正確值才會覺得奇怪,這一部份是實作的差異,我個人覺得這是VCL中服務定義和實作不太一致的地方,在書籍再版中會以註釋向讀者說明. 我不覺得是書中說明的錯誤,因為VCL這樣的設計違反了OCP規範,只是書中沒有補充說明清楚.
      

  22.   


    还有一个疑问,是对第三个问题的。VMT 跟 RTTI 是两个不同的东西吧?RTTI是放在VMT中的吗?。。如果不是,那么从RTTI中得到ClassName 并不表示VMT已经建立了呀。。
      

  23.   

    class function TObject.ClassInfo: Pointer;
    begin
      Result := PPointer(Integer(Self) + vmtTypeInfo)^;
    end;RTTI 表的首地址是放在 VMT 中的。