======================================================
注:本文源代码点此下载
======================================================
前几天开始阅读 vcl 源代码,可是几个基类的继承代码把我看得头大。在大富翁请教了几位仁兄后,我还是对delphi对象的创建和方法调用原理不太清楚。最后只好临时啃了一下汇编,把delphi对象操作的几个关键的方法勘察了一遍。
你可以通过以下链接知道我为什么要做这件事:
http://www.delphibbs.com/delphibbs/dispq.asp?lid=2385681
这是我花费一个晚上的测试结果,更多的细节只能以后在学习中再去了解。
主要测试项目为:
⊙ 测试目标:查看 tobject.create 的编译器实现
⊙ 测试目标:查看 constructor 函数中 inherited 的编译器实现
⊙ 测试目标:以 object reference 和 class reference 调用构造函数的编译器实现
⊙ 测试目标:考查 object 和 class 在调用 class method 时的编译器实现
⊙ 测试目标:考查 shortstring 返回值类型的函数没有赋值时编译器的实现
我把测试的细节记录在后文,一是自己留作参考,二是给对此有兴趣的朋友参考。其实更重要的是,大家可以帮忙检查我的分析有没有错误。我一直是用 delphi 的组件拖放编程,真正的功底只是这几天阅读 object pascal reference 和 vcl 得来的,汇编更是临时抱佛脚,所以错误难免。我清楚自己的水平,所以写下结论后非常担心。尽管如此,我的目的是为了学习,希望你发现错误后帮我指出来。
主要的结论是:
(*) tobject.create确实是个空函数,borland 并没有隐藏 tobject.create 的代码。tobject.create的汇编代码是由 constructor directive 指示编译器形成的,编译器对每个class 都一视同仁。
(*) dl 和 eax 是 constructor create 实现的关键寄存器。borland 将对象的创建过程设计得精妙而清晰(个人感觉,因为我不知道其他的语言比如c++是如何实现的)。
(*) 一个对象的正常的创建(obj := tmyclass.create)过程是这样的:
1. 编译器保证第一个 constructor 调用之前 dl = 1
编译器保证 inherited create 调用之前 dl = 0
2. dl = 1 时 编译器保证 create 时 eax = pointer to class vmt
dl = 0 时 编译器保证 create 时 eax = pointer to current object
3. 编译器保证任何层次的 constructor 调用后 eax = pointer to current object
4. dl = 1 时 编译器保证 create 调用 system._classcreate,并与 constructor 相同的方式使用 eax
dl = 1 时 编译器保证 create 调用 system._afterconstruction,并且调用前后 eax = pointer to current object
dl = 0 时 编译器保证 create 不会调用 system._classcreate
dl = 0 时 编译器保证 create 不会调用 system._afterconstruction
5. system._classcreate 中设置结构化异常处理,在 create 即将结束时关闭结构化异常处理。
如果出错则会(1)释放由编译器分配的内存(2)恢复堆栈至创建对象之前(3)调用 tsomeclass.destroy。
(*) object reference 方式的 constructor 调用,编译器尝试实现为 inherited 调用,结果当然是错误。
(*) class method 的调用隐含参数 eax 为指向 vmt 的指针,不管是用 class 还是 object 方式调用,编译器都会正确地把指向 class vmt 的指针传递给 eax。
要读懂下文的测试过程,可能需要相关基础,推荐阅读 object pascal reference 以下章节:
parameter passing
function results
calling conventions (register缺省调用约定,constructor 和 destructor 函数必须采用 register 约定)
inline assambly code
《delphi的原子世界》非常值得一读。
以下是测试内容:
=================================================
⊙ 测试目标:查看 tobject.create 的编译器实现
=================================================
⊙ 测试代码及反汇编代码:
procedure test; register;
var
obj: tobject;
begin
push ebp// 前2句用于设置堆栈指针
mov ebp, esp
push ecx//保存 ecx (无用的语句)
obj := tobject.create;
mov dl, $01//设置 dl = 1,通知 tobject.create 这是一次新建对象的调用
mov eax, [$004010a0]// 把指向 tobject class vmt 的指针存入 eax,
//作为 tobject.create 隐含的 self 参数
call tobject.create// 调用 tobject.create 函数
mov [ebp-$04], eax// tobject.create 返回新建对象的指针至 obj
end;
pop ecx// 恢复堆栈并返回
pop ebp
ret
⊙ tobject.create 的反汇编代码:
// 函数进入时 eax = pointer to vmt(dl = 1)
eax = pointer to instance(dl = 0)
// 函数返回时 eax = pointer to instance
test dl, dl// 检查 dl 是否 = 0
jz +$08// dl = 0则跳至 @@1
add esp, -$10// 增加 16 字节的堆栈,每次调用 _classcreate 之前都会进行
// 用于 system._classcreate 设置结构化异常处理
call @classcreate// 调用 system._classcreate
@@1:
test dl, dl// 检查 dl 是否 = 0
jz +$0f// dl = 0则跳到 end 结束过程
call @afterconstruction// dleax = pointer to vmt}
{eax vmt}
{edx pointer to result string}
pushesi
pushedi
movedi,edx// edx 是返回值串的指针
movesi,[eax].vmtclassname
xorecx,ecx
movcl,[esi]// 设置 result string 的 length
incecx
repmovsb
popedi
popesi
end;
{$endif}
结论:这只是我想了解字符串返回值的传递方式。
===================
(完)
======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/