在《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 已经建立起来了。
{ ======================================================== }
当然,由于小弟的水平所限,有些"问题"可能是我自己的错,请大家海涵!
问题一:书中的论述是这样的:
{ ======================================================== }
让我们看一个范例,在下面的程序代码中声明了如下的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 已经建立起来了。
{ ======================================================== }
解决方案 »
- 求助!关于Rzchecktree控件的使用
- Delphi2009的指针操作十分混乱,或者应该算严重的Bug!
- form2:=TForm2.create(nil)的奇怪问题
- 在image上画圆时边缘出现锯齿现象,如何把边缘变的圆滑
- Delph下有哪些实体映射的开源项目
- 请问‘ floating point divisionby zero' ' invalid floating point operation' 错误原因是什么?
- 100分:stack overflow
- 向各位高手指教HTML格式邮件的接收和显示问题!
- 求解并发问题
- 怎么把动态添加的image存进list中,需要用的时候再调用,谢谢大神们
- 在多个线程中运行同一个函数,会不会发生混乱?
- 各位高手来指教菜鸟问题,哪里有好的DELPHI7的关于RAVE报表教程下载,急!!!
// 但有一点他没有注意到 TComponent 的构造函数 Create 是 virtual 的。
// 而 TObject.Create 却不是 virtual 的。李大哥的书我没钱买,不过构造函数跟你虚不虚是没关系的,它实际是一个类方法(即使是通过实例的调用它也只相当于一个普通的方法而已),你把他的那段例子贴出来,咱哥们研究研究
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;
解决一个问题50美刀,谢谢!
// 当我们使用通常的对象创建方法,即 aBObj := TBase.Create;
// 此时,当对象构造完毕时 aBObj.iRef 是等于 100 的。
// 但如果按照现在的写法,aBObj.iRef 仍然为 0。 // 这个地方我加一句,
// 如果“TBase 的基类”的 Create 是 virtual 的,
// 而且 TBase.Create 进行了 override,那么就没什么问题了。 我不知道这样说 forgetter()兄能不能明白我的意思,但是兄可以做个简单的程序来测试一下。 我得出的结论就是我自己先前测试的结果。
作为对象方法来调用你就把它当作普通的方法看待(实际当然不是这么简单),当然也就具有你说的多态特性
另外就是forgetter推荐的李战的《悟透Delphi-DELPHI的原子世界》另外我再推荐考鸡翅膀兄马洪喜的那篇文章:对Object Pascal编译器给类对象分配堆内存细节的一种大胆猜测http://www.csdn.net/develop/author/netauthor/mahongxi/
你留下你的Email,我把大哥写的那篇文章发给你。嘿嘿,里面有一些是我写的呢。对了,FS,上次你不是说对我说的Object pascal先建立子类再建立父类不敢苟同吗?你也留下Email。我发给你。
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等方法来实现的。但是李维这么说还是有点不妥。
>是,我已經知道這個問題,Inside VCL一些打字錯誤和這個問題會在再版中改在,不過我想簡體版應該會有再版,繁體版可能就沒有了.有關MethodName/MethidAddress的問題不一定是書的錯誤,而是服務定義和實作的不同.在根物件中定義的方法應該是基礎服務,特別是當MethodName/MethodAddreess被定義成class method時.依照class method的基本含意,所有Object Instance或是藉由class都可以呼叫, 因此MethodName/MethidAddress根據Contract應該是需要回傳正確數值的.然而對於由他物件來呼叫不會有結果在書中不會回傳正確值才會覺得奇怪,這一部份是實作的差異,我個人覺得這是VCL中服務定義和實作不太一致的地方,在書籍再版中會以註釋向讀者說明. 我不覺得是書中說明的錯誤,因為VCL這樣的設計違反了OCP規範,只是書中沒有補充說明清楚.
还有一个疑问,是对第三个问题的。VMT 跟 RTTI 是两个不同的东西吧?RTTI是放在VMT中的吗?。。如果不是,那么从RTTI中得到ClassName 并不表示VMT已经建立了呀。。
begin
Result := PPointer(Integer(Self) + vmtTypeInfo)^;
end;RTTI 表的首地址是放在 VMT 中的。