请先看下面的测试:Type
TExample = class
private
FMessage: string;
public
constructor Create;
procedure ShowIt;
end;constructor TExample.Create;
begin
inherited;
FMessage := 'Hello';
end;procedure TExample.ShowIt;
begin
ShowMessage('I''m TExample');
end;测试 1:
procedure TForm1.Button7Click(Sender: TObject);
var
obj: TExample;
begin
obj.ShowIt; // I'm TExample
end;问题:
1. 对象还没有创建,为什么不报错?
2. 如果 ShowIt 方法是 ShowMessage(FMessage),则报错? //测试 2:
procedure TForm1.Button1Click(Sender: TObject);
var
obj: TExample;
P1, P2, P3, P4: Pointer;
begin
obj := TExample.Create;
P1 := Pointer(Integer(TExample) - 76);
TExample(P1).ShowIt; //I'm Example P2 := TExample;
TExample(@P2).ShowIt; //I'm Example P3 := Pointer(Integer(obj) - 76);
TExample(P3).ShowIt; //I'm Example P4 := Pointer(Integer(TExample) + TObject.InstanceSize);
TExample(P4).ShowIt; //I'm Example ShowMessage(Format('%d', [Integer(P1) - 76])); //4570620
ShowMessage(Format('%d', [Integer(@P2)])); //1243000
ShowMessage(Format('%d', [Integer(P3)])); //13395420
ShowMessage(Format('%d', [Integer(P4)])); //450776 obj.Free;
end;问题:3. 为什么 P1,P2,P3,P4 所指向的地址不同,但是却调用了同一个
函数 ShowIt()?多运行几次,P1,P2,P3,P4 所指的地址也会发生变化?
4. 这 4 个指针所指向的地址是什么意思?
5. 类的实例的地址,类的地址(TExample),类的地址的地址(@TExample)
他们3者之间有什么联系?
//测试 3:1) 将 TExample.ShowIt 改为:procedure TExample.ShowIt;
begin
ShowMessage(FMessage);
end;2) 测试:procedure TForm1.Button2Click(Sender: TObject);
var
obj: TExample;
P1, P2, P3, P4: Pointer;
begin
obj := TExample.Create;
P1 := Pointer(Integer(TExample) - 76);
TExample(P1).ShowIt; //显示的是一些空格'' P2 := TExample;
TExample(@P2).ShowIt; //?? P3 := Pointer(Integer(obj) - 76);
TExample(P3).ShowIt; //显示的是一些空格'' P4 := Pointer(Integer(TExample) + TObject.InstanceSize);
TExample(P4).ShowIt; //出错: Access violation at address 0040453C in
//module 'Project2.exe'. Read of
//address 107FFFFC. ShowMessage(Format('%d', [Integer(P1) - 76])); //4570620
ShowMessage(Format('%d', [Integer(@P2)])); //1243000
ShowMessage(Format('%d', [Integer(P3)])); //13395420
ShowMessage(Format('%d', [Integer(P4)])); //450776 obj.Free;
end;问题:同测试 2
TExample = class
private
FMessage: string;
public
constructor Create;
procedure ShowIt;
end;constructor TExample.Create;
begin
inherited;
FMessage := 'Hello';
end;procedure TExample.ShowIt;
begin
ShowMessage('I''m TExample');
end;测试 1:
procedure TForm1.Button7Click(Sender: TObject);
var
obj: TExample;
begin
obj.ShowIt; // I'm TExample
end;问题:
1. 对象还没有创建,为什么不报错?
2. 如果 ShowIt 方法是 ShowMessage(FMessage),则报错? //测试 2:
procedure TForm1.Button1Click(Sender: TObject);
var
obj: TExample;
P1, P2, P3, P4: Pointer;
begin
obj := TExample.Create;
P1 := Pointer(Integer(TExample) - 76);
TExample(P1).ShowIt; //I'm Example P2 := TExample;
TExample(@P2).ShowIt; //I'm Example P3 := Pointer(Integer(obj) - 76);
TExample(P3).ShowIt; //I'm Example P4 := Pointer(Integer(TExample) + TObject.InstanceSize);
TExample(P4).ShowIt; //I'm Example ShowMessage(Format('%d', [Integer(P1) - 76])); //4570620
ShowMessage(Format('%d', [Integer(@P2)])); //1243000
ShowMessage(Format('%d', [Integer(P3)])); //13395420
ShowMessage(Format('%d', [Integer(P4)])); //450776 obj.Free;
end;问题:3. 为什么 P1,P2,P3,P4 所指向的地址不同,但是却调用了同一个
函数 ShowIt()?多运行几次,P1,P2,P3,P4 所指的地址也会发生变化?
4. 这 4 个指针所指向的地址是什么意思?
5. 类的实例的地址,类的地址(TExample),类的地址的地址(@TExample)
他们3者之间有什么联系?
//测试 3:1) 将 TExample.ShowIt 改为:procedure TExample.ShowIt;
begin
ShowMessage(FMessage);
end;2) 测试:procedure TForm1.Button2Click(Sender: TObject);
var
obj: TExample;
P1, P2, P3, P4: Pointer;
begin
obj := TExample.Create;
P1 := Pointer(Integer(TExample) - 76);
TExample(P1).ShowIt; //显示的是一些空格'' P2 := TExample;
TExample(@P2).ShowIt; //?? P3 := Pointer(Integer(obj) - 76);
TExample(P3).ShowIt; //显示的是一些空格'' P4 := Pointer(Integer(TExample) + TObject.InstanceSize);
TExample(P4).ShowIt; //出错: Access violation at address 0040453C in
//module 'Project2.exe'. Read of
//address 107FFFFC. ShowMessage(Format('%d', [Integer(P1) - 76])); //4570620
ShowMessage(Format('%d', [Integer(@P2)])); //1243000
ShowMessage(Format('%d', [Integer(P3)])); //13395420
ShowMessage(Format('%d', [Integer(P4)])); //450776 obj.Free;
end;问题:同测试 2
解决方案 »
- 监控数据库更新
- 怎么判断数字是否是偶数?
- combobox的问题
- 窗体里面有多个各种各样的组件,而且都是隐藏的。怎么把其中Name为MyEdit组件显示出来?
- 用Delphi创建SQL Server数据库
- 很菜鸟的问题 :由好几个form组成的程序,用什么方法关闭整个程序? close好像只能关闭打开的窗口。
- 请问服务端的查询结果如何传给客户端的clientdataset?急!
- DCOM调用 XP系统下提示“占位程序接收到错误数据”,2000下运行正常,急!!!!!!!!!
- 如何才可以獲得MS-SQL中有多少人在線﹖
- 急!急!急!急!急!,想要高分,请进
- 驾校管理系统
- 一个Class可以作为另一个Class的成员吗?Delphi中如何实现?
1. 对象还没有创建,为什么不报错?
--因为你的ShowIt方法没有存取对象的数据 2. 如果 ShowIt 方法是 ShowMessage(FMessage),则报错?
--因为你存取了对象的数据,但是你的Self却错的 3. 为什么 P1,P2,P3,P4 所指向的地址不同,但是却调用了同一个
函数 ShowIt()?多运行几次,P1,P2,P3,P4 所指的地址也会发生变化?
--因为你调用的是静态的方法,地址是预知的,所以不论Self是什么,TExample(X).ShowIt都会执行。语句TExample(X).ShowIt的语义等同于:
Self := X;
Call TExample + OFFSET(ShowIt) ;这个地址是预知的 >>>多运行几次,P1,P2,P3,P4 所指的地址也会发生变化?
--我没调试你的代码,我不知道 4. 这 4 个指针所指向的地址是什么意思?
--第一个指针指向你的TExample类,其他的我不知道 5. 类的实例的地址,类的地址(TExample),类的地址的地址(@TExample)
他们3者之间有什么联系?
--你已经说得很清楚了
--第2个指针指向你的TExample类,其他的我不知道
2. 如果 ShowIt 方法是 ShowMessage(FMessage),则报错?
这个函数不涉及具体的对象数据所以不报错,ShowMessage(FMessage)时就报错了3 p1:vmt地址
@P2:指针的地址
p3:对象内存区内的用来指向vmt地址的数据区
p4:不知道指到哪去了他们调用的都是不涉及具体的对象数据的函数,多运行几次类在内存中分配的地址不一样了,
4。如上
5 类的实例的地址就是对象地址,它和类地址类不一样,没有必然联系
类的地址的地址(@TExample) 是一个指针指向类的地址。
谢谢您啊!
唉,刚才我提问时心里还还忐忒不安呢,谈不上什么研究,让您见笑了,只是看见别人
这么写,可是我就是看不懂,I'm too stupid ^-^ 你说的1,2,这个我知道,可是我不知道为什么会这样?
你说的3,那是静态方法,地址预知,这个我也知道,你后面那句,所以不论Self是
什么,TExample(X).ShowIt都会执行,这是什么意思?
我想知道的是那些地址究竟指向的是什么?
P3,P4指向的地方应该对你没有意义你看看Delphi语言帮助的内存管理部分的Class Types按理说,应该是P3才会不断变化,其他的都不会
procedure TExample.ShowIt;
begin
ShowMessage(FMessage);
end;
TExample(p1) 他尝试把这个地址当作一个对想象的地址,从这个地址的相对位置读出fmessage,这个值到底是什么我也不知道
P3 := Pointer(Integer(obj) - 76);
TExample(P3).ShowIt; //显示的是一些空格''
我的在这个地方就运行错误,这个地址本来是个对象的存储vmt地址的区域,把它当作一个对象处理就出错了。
谢谢你,可是我还是不太明白, 你说的3:
//p1:vmt地址
我想也是,但是,P3 呢?
你看:
P1 := Pointer(Integer(TExample) - 76);
P3 := Pointer(Integer(obj) - 76); //@P2:指针的地址
我知道是指向指针的地址,我是想知道类的地址和类的地址的地址他们究竟是什么样的
关系,为什么我上面那样写能够调用没有访问数据的方法? //p3:对象内存区内的用来指向vmt地址的数据区
这和 P1 有什么联系和区别?
对象内存区内的用来指向vmt地址的数据区是什么意思啊?我今天看了帮助中关于VMT部分的,可是没看明白是什么意思。
P3 := Pointer(Integer(obj) - 76);
TExample(P3).ShowIt; //显示的是一些空格''
我刚刚还试了,不会报错啊,只是显示出来的是乱78遭的东西,一堆空格, P4 := Pointer(Integer(TExample) + TObject.InstanceSize);
TExample(P4).ShowIt;
这样就报错:
Access violation at address 0040453C in module 'Project2.exe'.
Read of address 107FFFFC.
X.ShowIt;
语义是:
Self := X;
Call TExample + OFFSET ShowItX.ShowIt2;
语义是:
Self := X;
ShowIt2Ptr := Self^ + OFFSET ShowIt2
Call ShowIt2Ptr;所以当X = nil时,X.ShowIt能够执行到,但是对于X.ShowIt2语句, --
Self := nil;
ShowIt2Ptr := nil^ + OFFSET ShowIt2
当执行到nil^时就发生了页保护错,根本执行不到Call ShowIt2Ptr语句
大哥,我不懂汇编呀! 不过,我测试了一下,的确是这样。
procedure TForm1.Button7Click(Sender: TObject);
var
P: Pointer;
begin
P := nil;
TExample(P).ShowIt; //I'm TExample
end;
{ ***************** Declaration1 ********************* }
type
TSimpleObject = class(TObject)
fField1: Integer;
fField2: string;
public
procedure StaticProc;
procedure VirtualProc; virtual;
procedure VirtualProc2; virtual;
end;
procedure TSimpleObject.StaticProc;
begin
fField2 := 'StaticProc';
end;procedure TSimpleObject.VirtualProc;
begin
fField2 := 'VirtualProc';
end;procedure TSimpleObject.VirtualProc2;
begin
fField2 := 'VirtualProc2';
end;
{ ****************** End of Declaration1 ****************** }相当于于隐含的产生了这些声明(不准确,只是大概意思):
{ ************* Begin of PseudoDeclaration *************** }
type
TSimpleClassVMT = record
PtrToVirtualProc: Pointer; // OFFSET PtrToVirtualProc = 0
PtrToVirtualProc2: Pointer; // OFFSET PtrToVirtualProc2 = 4
end;
PSimpleClassVMT = ^TSimpleClassVMT; TSimpleClassVMT1 = record
... //好多个字段,我不一一声明了,你看帮助的关于内存管理的Class Type一节吧
PtrToNewInstance: Pointer;
PtrToFreeInstance: Pointer;
PtrToDestrory: Pointer;
ClassVMT: TSimpleClassVMT;
end; TPseudoSimpleObject = record
PtrToVMT: PSimpleClassVMT;
fField1: Integer;
fField2: string;
end;
PPseudoSimpleObject = ^TPseudoSimpleObject;
procedure _TSimpleObjectStaticProc(Self: PPseudoSimpleObject);
begin
Self^.fField2 := 'StaticProc';
end;procedure _TSimpleObjectVirtualProc(Self: PPseudoSimpleObject);
begin
Self^.fField2 := 'VirtualProc';
end;procedure _TSimpleObjectVirtualProc2(Self: PPseudoSimpleObject);
begin
Self^.fField2 := 'VirtualProc2';
end;
{ ************* Begin of PseudoDeclaration *************** }
在声明{ Declaration1 }以后,你再声明变量:
var
Obj: TSimpleObject;
可以理解为声明:
var
_obj: PPseudoSimpleObject;
语句
Obj.StaticProc;
可以理解为
_TSimpleObjectStaticProc(_obj);
语句
Obj.VirtualProc;
可以理解为于语句序列
var
PtrOfVirtualProc: Pointer;
PtrOfVMT: PSimpleClassVMT;
PtrOfVMT := _obj^.PtrToVMT;
PtrOfVirtualProc := PtrOfVMT^.PtrToVirtualProc;
PtrOfVirtualProc(_obj);
我不多说了,慢慢看,应该就是这回事,有错就指正
谢谢你,说起来真惭愧,我很不熟悉delphi,又隔了好久没看了,
内存管理的 Class Type 怎么找啊?
如果是Delphi7的,就找Delphi Language Guide3。Language Guide下面有个Memory management,再下面就找到Class Types了4。不用说了,打开来看
谢谢,找到了。
我先看看!
P3 执行了未知地址, INTEGER(OBJ) 是对象的地址 再 -76 那就不知道上哪去了
P4 它是VMT的地址再加上一个TOBJECT对象所占的内存大小,应该没什么实际意义
P4 应该有意义的,好像是 FMessage 的地址。
vmt的地址加上4应该是TExample类的第二个虚拟方法的入口
也就是fmessage的地址=pdword(integer(obj)+4)^
Pointer(integer(obj) + 4) = Pointer(Integer(obj) + TObject.InstanceSize);
我测试过了。
你来看,
procedure TForm1.Button7Click(Sender: TObject);
var
P: Pointer;
begin
P := nil; //P 赋值为任意值,下面调用 ShowIt()始终不出错 !
TExample(P).ShowIt; //I'm TExample
end;在执行:TExample(P).ShowIt; 时,是如何找到 ShowIt 方法的呢???
当你调用TExample(P).ShowIt时,系统实际所做的工作是TExample.ShowIt(P);
如果ShowIt中没用到成员变量的话,P有没有赋值都可正常运行;
如果ShowIt中用到了成员变量的话,系统需要根据P的值寻到类实例的入口,此时若P仍为nil的话,当然便会报错;
不知你有没注意到TObject.Free方法,它也是可以在实例为空的情况下运行的,原理如上。
有兴趣可以看一下TObject.Free汇编源码,便会明了。
每个类有其固定地址,在应用程序生命周期内不会改变。因此,你声明的
TExample类的地址在应用程序生命周期内是不变的。二、对象声明可以是全局的,也可以是局部的。不管哪一种情况,在调用
对象构造器即Create之前,应用程序为其分配的内存只有四字节,即一个
32位的指针。对于全局对象变量,缺省情况下应用程序对其赋值为空指针
nil ;对于局部对象变量,缺省情况下应用程序对其赋值为一个有效非空
指针,指向其所属类的地址。三、成功调用了对象构造器的对象,将根据其域的大小为其分配内存,而
将对象的值改变为指向其实例的指针,既不是空指针nil (全局对象缺省
值),也不是指向其所属类的地址的指针(局部对象缺省值)。类实例即
对象被创建后,对象的值指向实例所在的内存区域,该内存区域前四字节
一定是一个指针,该指针指向对象所属类的虚拟方法表(VMT) 。同一个
类的不同实例拥有各自的内存空间,这些空间只存放 VMT地址和对象各自
域值,这些对象通过相同的 VMT指针共享类的方法。四、改变对象值的途径,通常包括调用其构造器、调用其析构器、直接向
对象变量赋值。因此,对于您提出的几个问题,可以如下回答:问题1 和问题2 : obj作为局部对象变量,应用程序初始化其为一个指向
TExample类的指针,直到调用其构造器。因此 obj对象创建之前可以调用
其方法,但是如果被调用的方法试图访问对象的数据即域值,将发生非法
访问。准确地说,Delphi编译器已经提出警告,你就应当尽量避免这种情
况发生。对于全局对象变量,则不管调用的方法是否需要对象的域值,都
将发生非法访问,因为在未改变对象指针的值之前它总为空指针 nil。对于ShowIt不访问域FMessage的情况:问题3、4、5 :不管你对P1赋予何值,甚至是赋予空指针 nil,当你调用
TExample(P1).ShowIt 方法时,都不会发生错误,因为这里只需要肯定都
能根据类型强制转化得到ShowIt的入口,而调用ShowIt时不访问对象域的
值,因此不会判断P1到底是什么东西。P2、P3、P4的情况完全一样。你可
以常识将P1赋值为 nil,结果还是完全相同。对于ShowIt要访问域FMessage的情况:问题3、4、5 :执行TExample(P).ShowIt的内部过程可以描述为,首先会
根据TExample类找到ShowIt方法的入口地址,然后试图把P 作为实例对象
来获得FMessage的值,最后调用ShowMessage过程。因为P1、@P2、P3、P4
都没有指向obj指向的实例对象,所以都不会成功得到FMessage 的值,而
访问非法当然是很正常的。至于这些指针的值,可以如下解释:
P1:TExample类的地址的相对地址,偏移量为-72
P2:TExample的地址
P3:obj 指向的地址的相对地址,偏移量为-72
P4:TExample类的地址的相对地址,偏移量为TObject.InstanceSize关于偏移量-72这个特别的整数,是有关VMT的,你可以查阅Delphi联机帮助:
Delphi6 Help -> Object Pascal Reference -> Memory management -> Class types
Delphi7 Help -> Delphi Language Guide -> Memory management -> Class types
cnsuyong(小可) 所说的真是不谋而合!其实 TExample 就是一堆方法的入口地址的指针集合,也就是 VMT,
所以,可以这么说 类 = VMT,类是一个全局变量,不同的对象(同一个类的实例)调用其方法时,调用的都是类的方法,
也就是说,同一个类的多个实例,他们的方法只有一份,其指针就在类的 VMT 的中。我查了一些资料,我觉得很多地方有一种说法好象不是很好:“声明 一个类时,类的方法的地址已经是固定的了”我觉得这句话好象不太对,我觉得应该是“申明一个类后,在编译时,类的方法的地址
相对进程的内存空间来说是固定的”
因为,我们运行一个程序时,其实是创建一个进程,进程拥有获得cpu时间片的权利,
(当OS采用时间片轮训策略分配处理机时)内存空间...以及其他的资源。然后启动主线程,
创建类,这个类是全局的,类的方法的地址相对于进程的内存空间是固定的,而进程的
内存空间在我们每次运行程序前都是不确定的。不知道我说的是不是这样的。
你说的对,所谓的2G内存空间,其实也是虚拟内存,现在谁的PC有2G的内存啊?
对我们来说是透明的。操作系统和CPU协同处理了虚拟地址和物理地址的转换。你试一下,随便编一个简单程序,只是分配块内存区域,然后显示该已经分配地址的指针的地址值,编译后多运行几次,看看地址是否会发生变化。
你说的帮助我看了几遍,昨天我是用 VMT 索引查询到的。
Delphi6 Help -> Object Pascal Reference -> Memory management -> Class types现在可以确定,
类的方法是通过类的 VMT 来找到其入口地址的,再问你一个问题,类的一般的普通方法,也就是非虚方法的入口地址在什么地方?
刚开始,我以为是在vmtMethodTable(- 52 )的地方。 vmtMethodTable = -52;可是,我今天看到一篇文章,是这样说的:RTTI则是由ClassName、TypeInfo、FieldTable、MethodTable组成的,
注意只有publish的field和method才会进入FieldTable和MethodTable,
所以,显然的只有publish的成员才存在RTTI信息。
如果这段话是正确的,那么,还是以我上面的例子,
ShowIt()方法是 Public 的,不是 Publish的,
那么,它的入口地址就不会在vmtMethodTable所指向的地址空间内,
ShowIt() 方法的入口地址究竟在 VMT 的什么地方呢?
我看了几遍,几遍,觉得可能在 vmtMethodTable - 52 处,可是
看到那篇文章说,vmtMethodTable处是 publish 部分申明的方法,
那么,如果这样的话,类的普通方法入口究竟在 VMT 的什么地方? vmtSelfPtr = -76; //VMT
vmtIntfTable = -72; //interface table
vmtAutoTable = -68; //Automation information table
vmtInitTable =-64; //instance initialization table
vmtTypeInfo = -60; //RTTI (type information table )
vmtFieldTable = -56; //RTTI (publish部分的:field definition table )
vmtMethodTable = -52; //RTTI (publish部分的:method definition table )
vmtDynamicTable = -48; //DMT
vmtClassName = -44; //RTTI ClassName
vmtInstanceSize = -40; //instance size in bytes
vmtParent = -36; //pointer to a pointer to ancestor class
vmtSafeCallException = -32; //[-32 → -4]是 TObject 的虚方法
vmtAfterConstruction = -28;
vmtBeforeDestruction = -24;
vmtDispatch = -20;
vmtDefaultHandler = -16;
vmtNewInstance = -12;
vmtFreeInstance = -8;
vmtDestroy = -4;
...
下面是用户定义的虚方法入口指针
类的非虚方法究竟在 VMT 的什么地方,究竟如何得到非虚方法的入口指针呢?
究竟在哪里???
刚开始,我也是这么认为的,可是,今天上午我看到一篇文章,说只有
Publish 部分的方法才在那里,那public 和 private 部分的呢?
PtrToStaticProc := @TAClass.StaticProc;
语句就可以了。至于它们的位置,是由编译器根据你编写的各个方法的顺序来决定的,你稍为改变Implemenation节以下的函数之间的相对位置,他们的地址就相应变化。我猜测Delphi没有把non-published的静态方法的地址信息储存在VMT中,因为储存在那里没有必要。
举个例子:
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure Test;
end;...procedure TForm1.Button1Click(Sender: TObject);
begin
// 下面代码直接于直接调用Test;
asm
mov eax, Self
call TForm1.Test
// 如果是虚方法的话,还需要跟据eax值算出实际地址,
// 但Test是静态方法,直接引用即可
end
end;procedure TForm1.Test;
begin
ShowMessage('Hello, World!');
end;
唉,竟然猜错了!
westfly(西翔)://VMT里没有普通方法,因为普通方法地址已确定,不需要再查VMT表确定。虽然已经确定,但是编译器如何查到非虚方法的地址的?
也就是说,如何根据非虚方法的名称影射到这个方法所对应的地址?
//PtrToStaticProc := @TAClass.StaticProc; StaticProc 哪里有啊? 我不是想取得非虚方法的地址,而是想知道编译器如何根据非虚方法名称影射到
非虚方法的在编译期间就确定了的地址,因为我以前遇到一个非常变态的问题,
我百思不得其结,因此,来这里请教,想知道编译器到底是如何调用一个方法的。 //Delphi没有把non-published的静态方法的地址信息储存在VMT中,
//因为储存在那里没有必要。
你觉得 published 的方法存储在那里的意义是什么呢?
记得上次我问了一个相似的问题,他就给了我非常好的解决方法,真是非常的感谢他!
这个问题,其实,也是因上次的问题而起。
---------------------------------------------------
我只是举个例子,在你的TExample中就是
PtrToShowIt := @TExample.ShowIt;
我不是想取得非虚方法的地址,而是想知道编译器如何根据非虚方法名称影射到
非虚方法的在编译期间就确定了的地址,因为我以前遇到一个非常变态的问题,
我百思不得其结,因此,来这里请教,想知道编译器到底是如何调用一个方法的。 //Delphi没有把non-published的静态方法的地址信息储存在VMT中,
//因为储存在那里没有必要。
你觉得 published 的方法存储在那里的意义是什么呢?---------------------------------------------------
>>而是想知道编译器如何根据非虚方法名称影射到非虚方法的在编译期间就确定了的地址这个有点费口舌,我不多说了,反正你暂时只需知道编译器在编译的时候遇到方法调用的符号
会直接把它替换成地址,运行时是不会动态去查找的。你以后多编程序慢慢会理解的,别太着急。如果你要根据方法名得到方法的地址,那你只能将方法published。
published的方法的意义我只知道的就是动态调用方法。动态得到方法地址和调用该方法,比如:program project21;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
TExample2 = class(TPersistent) //注意,必须继承于TPersistent或其后裔,
//或者在$M+状态下声明。
private
fMessage: string;
published
procedure Test;
end;procedure TExample2.Test;
begin
WriteLn(fMessage);
fMessage := 'Message modified.';
end;procedure CallObjectProcNoParam(aObj: TExample2; aPtrOfTest: Pointer);
//注意,这个函数只对没有参数的过程适用,有参数或有返回值必须另外再写
asm
call aPtrOfTest;
end;var
PtrOfTest: Pointer;
Obj: TExample2;
begin
Obj := TExample2.Create;
try
Obj.fMessage := 'message';
PtrOfTest := Obj.MethodAddress('Test');
CallObjectProcNoParam(Obj, PtrOfTest);
CallObjectProcNoParam(Obj, PtrOfTest);
finally
Obj.Free;
end;
Readln;
end.
谢谢你啊,我不太会写程序,刚开始学不久,中间又有事情耽搁了一段时间,
这几天我正在狠看 Object Pascal ,以后多教教我啊!
明天再来结这个帖子!
TExample = class(Tobject)
private
FMessage: string;
procedure Proc1;
protected
procedure Proc2;
public
constructor Create(S: string); virtual;
procedure Proc3;
published
procedure Proc4;
end;constructor TExample.Create(S: string);
begin
inherited Create;
FMessage := S;
end;procedure TExample.Proc1;
begin
ShowMessage(FMessage + ' Proc1');
end;procedure TExample.Proc2;
begin
ShowMessage(FMessage + ' Proc2');
end;procedure TExample.Proc3;
begin
ShowMessage(FMessage + ' Proc3');
end;procedure TExample.Proc4;
begin
ShowMessage(FMessage + ' Proc5');
end;procedure TForm1.Button1Click(Sender: TObject);
var
E: TExample;
Proc: procedure of object;
PI: PInteger;
procedure ShowAddress(S: string; P: Pointer = nil);
begin
if P = nil then
Memo1.Lines.Append(S)
else
Memo1.Lines.Append(S + Format('%8x', [Integer(P)]));
end;
begin
Memo1.Lines.Clear;
E := TExample.Create('This is ');
ShowAddress('TExample is at ', TExample);
Proc := E.Proc1; ShowAddress('Proc1 is at ', @Proc);
Proc := E.Proc2; ShowAddress('Proc2 is at ', @Proc);
Proc := E.Proc3; ShowAddress('Proc3 is at ', @Proc);
Proc := E.Proc4; ShowAddress('Proc4 is at ', @Proc);
ShowAddress('E is at ', @E);
ShowAddress('E''s size is ' + IntToStr(E.InstanceSize) + ' bytes');
ShowAddress('E pointes to ', E);
ShowAddress('E.FMessage is at', @E.FMessage);
PI := Pointer(E);
ShowAddress('E''s VMT is at ' + Format('%8x', [PI^]));
E.Free;
end;{
Delphi7下运行结果如下(注释是俺自己加的):TExample is at 454340 //这个地址就是所谓类的地址
Proc1 is at 4543EC //这是Proc1 的入口点
Proc2 is at 45444C //...
Proc3 is at 4544AC //...
Proc4 is at 45450C //...
E is at 69F328 //这是E 在内存中的位置,它是一个指针,只占用4 字节内存
E's size is 8 bytes //获得E 所指向的实例的尺寸, 8字节包括 4字节的VMT
//和 4字节的域FMessage(string类型也是一个指针)
E pointes to BD2518 //这就是实例E 所在的内存块位置
E.FMessage is at BD251C //这就是FMessage指针所在的位置
E's VMT is at 454340 //这就是E 所在的内存块前 4字节表示的指针值,一定指向TExample说明:也许你得到的地址值有些不同,但它们的特征是一样的。至于private、protected、public、published的区别,只是在编译环境中有区别,
在运行时各层次的域和方法都是被加载到内存中的。所谓 VMT,应当理解为虚拟的
方法表,而不应理解为虚拟方法的表,这里的“虚拟”与“虚拟键盘”中“虚拟”
的含义一致,而与Delphi中的virtual方法所指的“虚拟”是无关的。至于RTTI,对于Delphi ITE/IDE,关键是published与public的区别。对于属性和事件,只有
当它们被声明为published时,才能在设计时被产生为RTTI,这也是Delphi最早充分体现可视
化设计的关键优势。除此之外,public与published成员没有任何区别。因此,对于方法,将
其声明为published成员是没有任何意义的,尽管合法并且可以正常使用。
原汁原味的解释,你可以参考下列主题以及相关主题:Delphi6 Help -> Object Pascal Reference -> Classes and objects -> Class types ->
Visibility of class members -> Published members
Delphi7 Help -> Delphi Language Guide -> ...}
如有谬误,欢迎指正。
http://expert.csdn.net/Expert/topic/1523/1523950.xml?temp=.9614069
其实我一直在偷看阿,呵呵
学习
这个我觉得直接写就可以
ShowAddress('Proc1 is at ', @TExample.Proc1);
ShowAddress('Proc2 is at ', @TExample.Proc2);
ShowAddress('Proc3 is at ', @TExample.Proc3);
ShowAddress('Proc4 is at ', @TExample.Proc4);
您写的代码没错。我之所以绕点路,是为了说明对象指针、对象实例、类的方法这三者之间是如何通过指针联系起来的。
ShowAddress('E''s VMT is at ' + Pointer(Pointer(E)^));
也可以这样:
ShowAddress('E''s VMT is at ' + PPointer(E)^);只是写法不同而已!另外:
从上面可以看得出,类的方法是 early-bind 在类指针上的。
对于你第一次回复中的第二点:“ 对于局部对象变量,缺省情况下应用程序对其赋值为一个有效非空
指针,指向其所属类的地址。” 我昨天晚上回家测试了一下,怎么和你说的不一样,是不是我测试的方法不对?
我的测试代码如下:
(我测试了两个,用以对比它们的值)创建了的局部对象变量所指向的地址:
var
E: TExample
begin
E := TExample.Create;
ShowAddress('E is at ', TExample); //类指向的地址→VMT
ShowAddress('E is at ', PPointer(E)^);); //对象头4个字节指向的地址→VMT
E.Free;
end;//上面两个值是相等的,都是指向类的地址。没有创建的局部对象变量默认指向的地址:
var
E: TExample
begin
ShowAddress('obj is at ', TExample); //类指向的地址 → VMT
ShowAddress('obj is at ', PPointer(E)^);); //一个负的值
end;测试结果不一样,后者是一个负值,
我记不清具体的值,反正,我上面这样测试,后面一个是一个负的值。cnsuyong(小可),如果有时间,请你再看看如何?
我的结果是
obj is at 459694
obj is at 8BD88B53
对于下面的代码
procedure TMainForm.Button2Click(Sender: TObject);
var
E: TExample;
begin
ShowAddress('obj is at ', TExample); //类指向的地址 → VMT
ShowAddress('obj is at ', PPointer(E)^); //一个负的值
end;
我打开CPU窗口,ShowAddress('obj is at ', PPointer(E)^);
汇编代码如下
mov ecx, [esi] //esi内容为00431BA8, ecx就为8BD88B53
mov edx, $00459840 //$00459840为'obj is at '的地址
mov eax, ebx //ebx保存的是TMainForm的实例值
call TMainForm.ShowAddress这个esi其实就是TButton.Click的地址,在cpu窗口的代码窗口,右键Go To Address
输入$00431BA8,可以看到跳到TButton.Click处
至于为什么会这样,主要就是当点击Button是,产生一系列的调用,Click为动态方法,ESI保留了它的地址知道OnClick里,这样对于那个未被初始化的局部对象变量,直接传了个[ESI]过去这就像下面的这个一样
procedure TMainForm.Button2Click(Sender: TObject);
var
E: TButton;
begin
TMainForm(E).Caption := 'asdfasd';
end;主要是有编译器内部产生的汇编码决定的
所以不要用这个未初始化的局部对象变量
以前就注意到局部对象变量却省不是为空,但当时没有找到规律,也觉得只要不去用他,
应该不会有什么特别严重的后果。
只是看见上面cnsuyong(小可)说缺省情况下应用程序对其赋值为一个指向其所属类的地址。
我就觉得很奇怪,因此就测试了一下发现不对。
不知道cnsuyong(小可)是如何得出那个结论的,希望他能再看到这个帖子。
你说得对,那个没有初始化的对象变量的确毫无意义。
你还记得上次那个问题吗?“跨过父类继承祖先类的方法”
要设置不在一个单元申明的类的私有变量,用 aimingoo 的跨单元访问私有域的方法很好。
你用汇编优化后的方法也非常高明!其实,还有一种办法可以做到。关于类的方法: (不是类方法,这里指object pascal中的静态方法,相当于C++的成员函数)
1: 带有隐含参数 self
2: 方法是方法,数据是数据。
3:Delphi的方法是一个结构
type
TMethod = record
Code: Pointer; //方法的入口地址
Data: Pointer; //类实例指针(Self参数)
end;我们调用类的方法,其实分为2个部分,
1:调用方法的代码,通过类来获得方法入口地址。
2:访问对象的数据(也可能不访问),通过类的方法的那个隐含参数Self来访问。我做了一个测试:
TMyObject = class
private
FName: string;
public
procedure ChangeName(AName: string);
procedure ShowPrivate;
end;procedure TMyObject.ChangeName(AName: string);
begin
FName := AName; //修改私有变量
ShowMessage('My name is to be changed to ' + AName);
end;procedure TMyObject.ShowPrivate;
begin
ShowMessage('My name is ' + FName);
end;procedure TForm1.Button1Click(Sender: TObject);
type
TCallFun = procedure(AName: string) of object;
var
E: TMyObject;
M: TMethod;
begin
E := TMyObject.Create;
M.Code := @TMyObject.ChangeName; //这里设置要访问的方法的入口地址
M.Data := @Pointer(E)^; //设置Self参数,即类实例指针
TCallFun(M)('Xbl');
E.ShowPrivate; //My name is Xbl
E.Free;
end;要调用祖先类的方法,将 M.Code 设置为祖先类的方法的入口地址就可以了。
我在家里测试过的,
xzgyb(老达摩),是不是这样的 ?
若有不对的地方,恳请指出!
M.Data := E就行吧