近日在看李维老师的<<Inside VCL>>,又有了两点疑惑,以前看<<delphi面向对象编程思想>>也有疑惑,不过现在又有了,呵呵,只不过是看名家的书,但心中感觉有些问题
不说不快,疑惑1:书中57~58中,由于没有配套光盘,只抄了部分的代码
大概的代码:
TDerived = class(TBase)
private
FData: Variant;
public
...
function PureMethod: string;published
function MyMethod1: string;
procedure MyMethod2;
property sData: Variant read FDate write FDate;
end;procedure TForm1.Button7Click(Sender: TObject);
var
aObj: TDerived;
sData: string;
aPtr: Pointer;
begin
sData := 'TDerived at ' + DateTimeToStr(Now);
aObj := TDerived.Create(sData, HashOf(sData));
...
ShowMethodAddress(aObj, 'MyMethod1');
aPtr := ShowMethodAddress(aObj, 'MyMethod2');
sData := MethodName(aPtr); //注意这块
sData := Format('%x : %s', [Integer(aPtr), sData]);
Memo1.Lines.Add(sData);
....
end;function TForm1.ShowMethodAddress(aObj: TDerived; const sData: string): Pointer;
var
aPtr: Pointer;
sResult: String;
begin
try
aPtr := aObj.MethodAddress(sData);
sResult := Format('%s : %x', [sData + '位于': ', Integer(aPtr)]);
Memo1.Lines.Add(sResult);
except
on E: Exception do
begin
sResult := Format('%s : %s', [sData, e.Message]);
Memo1.Lines.Add(sResult);
end;
end;
end;59页
书中有一段话
'有趣的是上面的程序代码可以通过调用MethodAddress取得published方法的地址,
但是对于其它种类的方法是无法取得方法地址信息的。如果我们反向调用MethodName
却无法取得某一地址的方法名称,这是有点奇怪的',59页又有一段代码
procedure TForm1.Button8Click(Sender: TObject);
var
sData: string;
aPtr: Pointer;
begin
aPtr := Pointer(@Self.OnClick);
sData := MethodName(aPtr);
sData := Format('%x : %s', [Integer(aPtr), sData]);
Memo1.Lines.Add(sData);
end;在60页
'因此我们可以推知MethodName似乎只会对从TComponent继承下来的类的published方法才有作用';这段话我感觉是有问题的,第一段代码之所以用MethodName取不到方法名,是因为在
TForm1.Button7Click(Sender: TObject)中直接调用sData := MethodName(aPtr);
相当于Self.MethodName(aPtr),也就是说调用TForm1的对象的MethodName
这当然找不到aObj的MyMethod2方法名,应该调用的是aObj.MethodName(aPtr);所以60页的这个结论不成立疑惑2:
93页中有个测试VMT表格创建的时机的代码.procedure TForm1.Button4Click(Sender: TObject);
var
aPn1: TPanel;
begin
aClass := aPn1.ClassType;
sClassName := 'TPanel';
ShowVMTContent(aClass); //显示VMT内容的一个函数,不再抄了
aPn1 := TPanel.Create(Self);
aClass := aPn1.ClassType;
sClassName := 'TPanel';
ShowVMTContent(aClass);
FreeAndNil(aPn1);
end;94书中说
'在未实际创建TPanel对象前我们经由aPn1显示VMT的内容,得到的结果应该是无意义的内容。然而一旦当TPanel对象实际在内存中创建之后就可以取得TPanel的VMT中的
内容.由这个现象来看,我们可以推知VMT应该是在第一个类对象被创建时才会创建完整的VMT内容,原因是属于同一个类的所有对象都是共享一个VMT表格'这话我又感觉有问题了,
aClass := aPn1.ClassType;
sClassName := 'TPanel';
ShowVMTContent(aClass);
这个之所以是无意义的内容,因为aPn1本身是一个指针变量,一开始指向的就是无意义的内容,而ClassType的代码如下
function TObject.ClassType: TClass;
begin
Pointer(Result) := PPointer(Self)^;
end;
所以这个取到的ClassType本身就是无意义的,
简单的来说
aClass := TPanel;
sClassName := 'TPanel';
ShowVMTContent(aClass);
这样就内容正确,这时并没有创建一个类的对象。
因此一个类的VMT创建的时机我觉得应该是在源码中第一次出现该类的名字时
创建的,换句话说应该是当编译器进行编译时发现你的源码中出现了TPanel这个词
那么就在内存中创建一个TPanel的VMT表.以上仅是自己的一点看法,并不定正确.书刚看了一点,继续看.
不说不快,疑惑1:书中57~58中,由于没有配套光盘,只抄了部分的代码
大概的代码:
TDerived = class(TBase)
private
FData: Variant;
public
...
function PureMethod: string;published
function MyMethod1: string;
procedure MyMethod2;
property sData: Variant read FDate write FDate;
end;procedure TForm1.Button7Click(Sender: TObject);
var
aObj: TDerived;
sData: string;
aPtr: Pointer;
begin
sData := 'TDerived at ' + DateTimeToStr(Now);
aObj := TDerived.Create(sData, HashOf(sData));
...
ShowMethodAddress(aObj, 'MyMethod1');
aPtr := ShowMethodAddress(aObj, 'MyMethod2');
sData := MethodName(aPtr); //注意这块
sData := Format('%x : %s', [Integer(aPtr), sData]);
Memo1.Lines.Add(sData);
....
end;function TForm1.ShowMethodAddress(aObj: TDerived; const sData: string): Pointer;
var
aPtr: Pointer;
sResult: String;
begin
try
aPtr := aObj.MethodAddress(sData);
sResult := Format('%s : %x', [sData + '位于': ', Integer(aPtr)]);
Memo1.Lines.Add(sResult);
except
on E: Exception do
begin
sResult := Format('%s : %s', [sData, e.Message]);
Memo1.Lines.Add(sResult);
end;
end;
end;59页
书中有一段话
'有趣的是上面的程序代码可以通过调用MethodAddress取得published方法的地址,
但是对于其它种类的方法是无法取得方法地址信息的。如果我们反向调用MethodName
却无法取得某一地址的方法名称,这是有点奇怪的',59页又有一段代码
procedure TForm1.Button8Click(Sender: TObject);
var
sData: string;
aPtr: Pointer;
begin
aPtr := Pointer(@Self.OnClick);
sData := MethodName(aPtr);
sData := Format('%x : %s', [Integer(aPtr), sData]);
Memo1.Lines.Add(sData);
end;在60页
'因此我们可以推知MethodName似乎只会对从TComponent继承下来的类的published方法才有作用';这段话我感觉是有问题的,第一段代码之所以用MethodName取不到方法名,是因为在
TForm1.Button7Click(Sender: TObject)中直接调用sData := MethodName(aPtr);
相当于Self.MethodName(aPtr),也就是说调用TForm1的对象的MethodName
这当然找不到aObj的MyMethod2方法名,应该调用的是aObj.MethodName(aPtr);所以60页的这个结论不成立疑惑2:
93页中有个测试VMT表格创建的时机的代码.procedure TForm1.Button4Click(Sender: TObject);
var
aPn1: TPanel;
begin
aClass := aPn1.ClassType;
sClassName := 'TPanel';
ShowVMTContent(aClass); //显示VMT内容的一个函数,不再抄了
aPn1 := TPanel.Create(Self);
aClass := aPn1.ClassType;
sClassName := 'TPanel';
ShowVMTContent(aClass);
FreeAndNil(aPn1);
end;94书中说
'在未实际创建TPanel对象前我们经由aPn1显示VMT的内容,得到的结果应该是无意义的内容。然而一旦当TPanel对象实际在内存中创建之后就可以取得TPanel的VMT中的
内容.由这个现象来看,我们可以推知VMT应该是在第一个类对象被创建时才会创建完整的VMT内容,原因是属于同一个类的所有对象都是共享一个VMT表格'这话我又感觉有问题了,
aClass := aPn1.ClassType;
sClassName := 'TPanel';
ShowVMTContent(aClass);
这个之所以是无意义的内容,因为aPn1本身是一个指针变量,一开始指向的就是无意义的内容,而ClassType的代码如下
function TObject.ClassType: TClass;
begin
Pointer(Result) := PPointer(Self)^;
end;
所以这个取到的ClassType本身就是无意义的,
简单的来说
aClass := TPanel;
sClassName := 'TPanel';
ShowVMTContent(aClass);
这样就内容正确,这时并没有创建一个类的对象。
因此一个类的VMT创建的时机我觉得应该是在源码中第一次出现该类的名字时
创建的,换句话说应该是当编译器进行编译时发现你的源码中出现了TPanel这个词
那么就在内存中创建一个TPanel的VMT表.以上仅是自己的一点看法,并不定正确.书刚看了一点,继续看.
呵呵,主要是现在在家呆着,上网不是很方便
所以不敢太多说话,呵
只有象我这样的菜鸟才会去翻帮助?还是我的理解力太差?恕菜鸟不知天高地厚、我怎么感觉是李维说了一大堆废话!If Address does not point to a published method of the object, MethodName returns an empty string.
对于第一个疑惑,就如楼上所言,MethodName只会返回一个published方法的类的函数
疑惑2、
每个类都是在Create执行时才分配内存地址空间的,不执行Create,当然得不到VMT
书的内容本身没错,拿那些例子得出一个结论就有点牵强了。
function _ClassCreate(AClass: TClass; Alloc: Boolean): TObject;
class function NewInstance: TObject;
function _GetMem(Size: Integer): Pointer;
......
“在60页
'因此我们可以推知MethodName似乎只会对从TComponent继承下来的类的published方法才有作用';” 这个结论明显是错误的。其测试代码:
procedure TForm1.Button8Click(Sender: TObject);
var
sData: string;
aPtr: Pointer;
begin
aPtr := Pointer(@Self.OnClick); //注意是取得Form1的点击事件处理方法指针,而此时Form1.OnClick处理方法根本不存在,自然返回空。如果书写了Form1.OnClick处理方法,自然可以正确返回其名字。
sData := MethodName(aPtr);
sData := Format('%x : %s', [Integer(aPtr), sData]);
Memo1.Lines.Add(sData);
end;
2、“我们可以推知VMT应该是在第一个类对象被创建时才会创建完整的VMT内容,原因是属于同一个类的所有对象都是共享一个VMT表格”结论也是错误的,帮助上说清楚了:“ There is exactly one VMT per class (not one per object); distinct class types, no matter how similar, never share a VMT. VMTs are built automatically by the compiler, and are never directly manipulated by a program”。既然和类对应,自然不应该和具体的对象有关,实际上,VMT保存的都是一个类的任意个对象的共性的东西,也不需要依赖于具体的对象信息而存在。我看过此书第三章电子版,总的感觉是作者有些矫柔造作,一些简单的地方故意分割绕述,反而让人更不明白。比如第三章讲覆盖,按照inherited出现位置的不同,强制划分出“3明治”和“逐漸增加法”等,感觉实在牵强(我将这个意见转告给了此书的技术编辑者,作者回信在书中增加相关注释,不知是否付诸实施)。书的技术编辑基本也是形同虚设,光注重修改一些错别字和文句语法问题了,而需要的技术验证确抛掷一边。
不过任何人的书都难免有错误,读者能有机会讨论清楚也就好了。————————————————————————————————————
宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
————————————————————————————————————
‘今年你拿了多少年终奖?http://expert.csdn.net/Expert/TopicView1.asp?id=2656356’
至于接着的一个结论:
“因此我们可以推知MethodName似乎只会对从TComponent继承下来的类的published方法才有作用”,我真的不知道是依据什么证据推得的。猜想可能与“类中没有显式指明作用域的成员,实际属于哪个域”的问题有关。比如:TForm1 = class(TForm)
procedure Button1Click(Sender: TObject);
private
……
end;其中,Button1Click到底应该属于哪个区域呢?是private、public还是published抑或其他?我曾经听过作者的一堂课,他讲是private。但实际上并不是这样简单,正确的是:在$M编译指令开启时,属于published区域,否则是public区域。 而在VCL中,$M是在TComponent的父类TPersistent处开启的(可以看Classes单元源代码)。加上其他一些阴差阳错的即时联想,作者轻易作出了上述错误结论。————————————————————————————————————
宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
————————————————————————————————————
本来我非常期望读读李维老师的<<Inside VCL>>,不过现在有一点失望
我看书有个坏习惯,如果书中出现一些明显的而不是印刷的错误,真的是没有
心情读下去
哎,总体这本书还是不错的,继续读下去.
对了,你的书什么时候出版,期待早日见到.
“因此我们可以推知MethodName似乎只会对从TComponent继承下来的类的published方法才有作用”应该改为“因此我们可以推知MethodName似乎只会对从TComponent 开始的类的published方法才有作用”
还有,设计时IDE会将你赋给组件事件的事件处理过程的名字存到 dfm 文件中,dfm 文件作为资源链接到Exe 文件中,运行时 Form 创建时从相应资源中读出过程的名字,利用TObject.MethodAddress方法查找到过程的代码入口再赋给组件的事件指针(注意事件是指针)。因此将 Button1Click移到非published段,则编译器不会为它生成 RTTI 信息,用 MethodAddress 方法则无法在运行时还原事件指针,将会出错。
{$M+}
TMyClass = class
procedure proc;
end;
{$M-}
中,你不会认为TMyClass是TComponent或其子类吧,也不会糊涂到认为MethodName对proc不起作用吧?!不管你技术多么高明,看你满嘴喷粪就知道你的综合素质差的很。所谓“乱世用能人”,当今太平世界,即使你技术好,但是修养如此之低,怕也难有多大作为……————————————————————————————————————
宠辱不惊,看庭前花开花落,去留无意;毁誉由人,望天上云卷云舒,聚散任风。
————————————————————————————————————
而且我用的是‘似乎’不是用的‘肯定’,不知道你上学的时间语文补考过几次?
小女娃娃就真是没有教养!
哦,打错了字,‘桂枝’,
我们这里谈的是DELPHI的VCL类库,仅VCL而言,你又扯到到那里去了呢?
而且我用的是‘似乎’,不是用的‘肯定’,不知道你小时候上学,语文补考过几次?
小女娃娃就是没有教养!
5416(CSDN版5415):对于你这个'delphi届太抖',呵呵,我不知道说啥,
我不说废话
写了个例子,因为我不会写程序代码,写的乱,将就试一下unit Unit1;interfaceuses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, TypInfo;type
TMyObject1 = class
private
FA: Integer;
FB: Integer;
FOnClick: TNotifyEvent;
public
constructor Create(AParent: TForm; ALeft, ATop: Integer);
published
MyButton1: TButton;
procedure MyProc1; property A: Integer read FA write FA;
property B: Integer read FB write FB;
property OnClick: TNotifyEvent read FOnClick write FOnClick;
end; {$M+}
TMyObject2 = class
private
FA: Integer;
FB: Integer;
FOnClick: TNotifyEvent;
public
constructor Create(AParent: TForm; ALeft, ATop: Integer);
published
MyButton2: TButton;
procedure MyProc2; property A: Integer read FA write FA;
property B: Integer read FB write FB;
property OnClick: TNotifyEvent read FOnClick write FOnClick;
end;
{$M-} TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
MyObject1: TMyObject1;
MyObject2: TMyObject2; procedure ShowInfo(AObject: TObject; const AM, AMethodName, AFieldName: string);
public
{ Public declarations }
end;var
Form1: TForm1;implementation{$R *.dfm}procedure TForm1.Button1Click(Sender: TObject);
begin
ShowInfo(MyObject1, '{$M-}', 'MyProc1', 'MyButton1');
end;{ TMyObject }constructor TMyObject1.Create(AParent: TForm; ALeft, ATop: Integer);
begin
MyButton1 := TButton.Create(AParent);
MyButton1.SetBounds(ALeft, ATop, 120, MyButton1.Height);
MyButton1.Parent := AParent;
end;procedure TMyObject1.MyProc1;
begin
ShowMessage('TMyObject1.MyProc1');
end;procedure TForm1.FormCreate(Sender: TObject);
begin
MyObject1 := TMyObject1.Create(Self, 0, 0);
with MyObject1.MyButton1 do
begin
MyObject2 := TMyObject2.Create(Self, Left + Width + 2, Top);
Memo1.SetBounds(Left, Top + Height + 2, Width * 2, Memo1.Height); Button1.SetBounds(Left + Width * 2 + 2, Top, Button1.Width, Button1.Height);
Button2.SetBounds(Left + Width * 2 + 2, Top + Button1.Height, Button1.Width, Button1.Height); Self.SetBounds(Self.Left, Self.Top, 2 * Width + Button1.Width + 10,
Height * 3 + Memo1.Height);
end;
Memo1.Lines.Clear;
end;procedure TForm1.FormDestroy(Sender: TObject);
begin
MyObject1.Free;
end;procedure TForm1.ShowInfo(AObject: TObject; const AM, AMethodName, AFieldName: string);
var
PMethod, PField,PInfo: Pointer;
PropList: PPropList;
I, PropCount: Integer;
begin
Memo1.Lines.Add('=======' + AObject.ClassName + ' ' + AM + ' =======');
PMethod := AObject.MethodAddress(AMethodName);
Memo1.Lines.Add(AObject.ClassName + '.' + AMethodName + '方法的地址: $' +
IntToHex(Integer(PMethod), 8));
Memo1.Lines.Add('地址$' + IntToHex(Integer(PMethod), 8) + '的方法名:' +
AObject.MethodName(PMethod));
PField := AObject.FieldAddress(AFieldName);
if PField <> nil then
begin
Memo1.Lines.Add(AObject.ClassName + '.' + AFieldName + '字段的地址: $' +
IntToHex(Integer(PField), 8));
TButton(PField^).Caption := AObject.ClassName + '.' + AFieldName;
end; PInfo := AObject.ClassInfo; if PInfo = nil then
Memo1.Lines.Add(AObject.ClassName + ' 无类型信息')
else
begin
Memo1.Lines.Add(AObject.ClassName + ' 有类型信息');
PropList := nil;
PropCount := GetPropList(PInfo, PropList);
Memo1.Lines.Add(AObject.ClassName + ' 有' + IntToStr(PropCount) + '个属性: ');
for I := 0 to PropCount - 1 do
Memo1.Lines.Add(' ' + PropList[I].Name);
if Assigned(PropList) then
FreeMem(PropList);
end;
end;
{ TMyObject2 }constructor TMyObject2.Create(AParent: TForm; ALeft, ATop: Integer);
begin
MyButton2 := TButton.Create(AParent);
MyButton2.SetBounds(ALeft, ATop, 120, MyButton2.Height);
MyButton2.Parent := AParent;
end;procedure TMyObject2.MyProc2;
begin
ShowMessage('TMyObject2.MyProc2');
end;procedure TForm1.Button2Click(Sender: TObject);
begin
Memo1.Lines.Add('');
ShowInfo(MyObject2, '{$M+}', 'MyProc2', 'MyButton2');end;end.分别用两个TMyObject1在{$M-}下,TMyObject2在{$M+}下
在$M无论是开还是关的情况下,MethodAddress, MethodName, FieldAddress
都取到正确的值
在{$M-}下,TMyObject1的ClassInfo为nil, 也就是它的类型信息为空,取不到该类的属性信息
在{$M+}下,TMyObject2的ClassInfo不为nil,可以取到该类的属性信息在delphi帮助中
ass is declared in the {$M-} state, and is not derived from a class that was declared in the {$M+} state, published sections are not allowed in the class.
我英语不太好
我现在用的版本是delphi6,是不是以前的版本
在{$M-}状态下,类中不允许出现published段
少写了MyObject2.Free;
procedure TForm1.FormDestroy(Sender: TObject);
begin
MyObject1.Free;
MyObject2.Free;
end;
是啊,好久不见了,还好吧,我也刚看这书不久.
5416:
我确实听不懂,没有办法,
不过看你这张爱说话的嘴,怎么看都不像是搞程序的
算了,我不爱和别人练嘴,我看书去了
结贴了