Delphi的对象机制浅探[转载]
Delphi的對(duì)象機(jī)制淺探
savetime2k@yahoo.com
2004-1-3
前幾天開始閱讀 VCL 源代碼,可是幾個(gè)基類的繼承代碼把我看得頭大。在大富翁請(qǐng)教了幾位仁兄后,我還是對(duì)Delphi對(duì)象的創(chuàng)建和方法調(diào)用原理不太清楚。最后只好臨時(shí)啃了一下匯編,把Delphi對(duì)象操作的幾個(gè)關(guān)鍵的方法勘察了一遍。
你可以通過以下鏈接知道我為什么要做這件事:
http://www.delphibbs.com/delphibbs/dispq.asp?lid=2385681
這是我花費(fèi)一個(gè)晚上的測(cè)試結(jié)果,更多的細(xì)節(jié)只能以后在學(xué)習(xí)中再去了解。
主要測(cè)試項(xiàng)目為:
⊙ 測(cè)試目標(biāo):查看 TObject.Create 的編譯器實(shí)現(xiàn)
⊙ 測(cè)試目標(biāo):查看 constructor 函數(shù)中 inherited 的編譯器實(shí)現(xiàn)
⊙ 測(cè)試目標(biāo):以 object reference 和 class reference 調(diào)用構(gòu)造函數(shù)的編譯器實(shí)現(xiàn)
⊙ 測(cè)試目標(biāo):考查 Object 和 Class 在調(diào)用 class method 時(shí)的編譯器實(shí)現(xiàn)
⊙ 測(cè)試目標(biāo):考查 ShortString 返回值類型的函數(shù)沒有賦值時(shí)編譯器的實(shí)現(xiàn)
我把測(cè)試的細(xì)節(jié)記錄在后文,一是自己留作參考,二是給對(duì)此有興趣的朋友參考。其實(shí)更重要的是,大家可以幫忙檢查我的分析有沒有錯(cuò)誤。我一直是用 Delphi 的組件拖放編程,真正的功底只是這幾天閱讀 Object Pascal Reference 和 VCL 得來的,匯編更是臨時(shí)抱佛腳,所以錯(cuò)誤難免。我清楚自己的水平,所以寫下結(jié)論后非常擔(dān)心。盡管如此,我的目的是為了學(xué)習(xí),希望你發(fā)現(xiàn)錯(cuò)誤后幫我指出來。
主要的結(jié)論是:
(*) TObject.Create確實(shí)是個(gè)空函數(shù),Borland 并沒有隱藏 TObject.Create 的代碼。TObject.Create的匯編代碼是由 constructor directive 指示編譯器形成的,編譯器對(duì)每個(gè)class 都一視同仁。
(*) dl 和 eax 是 constructor Create 實(shí)現(xiàn)的關(guān)鍵寄存器。Borland 將對(duì)象的創(chuàng)建過程設(shè)計(jì)得精妙而清晰(個(gè)人感覺,因?yàn)槲也恢榔渌恼Z言比如C++是如何實(shí)現(xiàn)的)。
(*) 一個(gè)對(duì)象的正常的創(chuàng)建(Obj := TMyClass.Create)過程是這樣的:
?? 1. 編譯器保證第一個(gè) constructor 調(diào)用之前 dl = 1
????? 編譯器保證 inherited Create? 調(diào)用之前 dl = 0
?? 2. dl = 1 時(shí) 編譯器保證 Create 時(shí) eax = pointer to class VMT
????? dl = 0 時(shí) 編譯器保證 Create 時(shí) eax = pointer to current object
?? 3. 編譯器保證任何層次的 constructor 調(diào)用后 eax = pointer to current object
?? 4. dl = 1 時(shí) 編譯器保證 Create 調(diào)用 System._ClassCreate,并與 constructor 相同的方式使用 eax
????? dl = 1 時(shí) 編譯器保證 Create 調(diào)用 System._AfterConstruction,并且調(diào)用前后 eax = pointer to current object
????? dl = 0 時(shí) 編譯器保證 Create 不會(huì)調(diào)用 System._ClassCreate
????? dl = 0 時(shí) 編譯器保證 Create 不會(huì)調(diào)用 System._AfterConstruction
?? 5. System._ClassCreate 中設(shè)置結(jié)構(gòu)化異常處理,在 Create 即將結(jié)束時(shí)關(guān)閉結(jié)構(gòu)化異常處理。
????? 如果出錯(cuò)則會(huì)(1)釋放由編譯器分配的內(nèi)存(2)恢復(fù)堆棧至創(chuàng)建對(duì)象之前(3)調(diào)用 TSomeClass.Destroy。
(*) object reference 方式的 constructor 調(diào)用,編譯器嘗試實(shí)現(xiàn)為 inherited 調(diào)用,結(jié)果當(dāng)然是錯(cuò)誤。
(*) class method 的調(diào)用隱含參數(shù) eax 為指向 VMT 的指針,不管是用 class 還是 object 方式調(diào)用,編譯器都會(huì)正確地把指向 class VMT 的指針傳遞給 eax。
要讀懂下文的測(cè)試過程,可能需要相關(guān)基礎(chǔ),推薦閱讀 Object Pascal Reference 以下章節(jié):
? Parameter passing
? Function results
? Calling conventions (register缺省調(diào)用約定,constructor 和 destructor 函數(shù)必須采用 register 約定)
? Inline assambly code
? 《Delphi的原子世界》非常值得一讀。
?
以下是測(cè)試內(nèi)容:
=================================================
⊙ 測(cè)試目標(biāo):查看 TObject.Create 的編譯器實(shí)現(xiàn)
=================================================
⊙ 測(cè)試代碼及反匯編代碼:
procedure Test; register;
var
? Obj: TObject;????????
begin
????????? push ebp???????????????????? ; 前2句用于設(shè)置堆棧指針
????????? mov ebp, esp
????????? push ecx???????????????????? ; 保存 ecx (無用的語句)
? Obj := TObject.Create;
????????? mov dl, $01????????????????? ; 設(shè)置 dl = 1,通知 TObject.Create 這是一次新建對(duì)象的調(diào)用
????????? mov eax, [$004010a0]???????? ; 把指向 TObject class VMT 的指針存入 eax,
?????????????????????????????????????? ; 作為 TObject.Create 隱含的 Self 參數(shù)
????????? call TObject.Create????????? ; 調(diào)用 TObject.Create 函數(shù)
????????? mov [ebp-$04], eax?????????? ; TObject.Create 返回新建對(duì)象的指針至 Obj
end;
????????? pop ecx????????????????????? ; 恢復(fù)堆棧并返回
????????? pop ebp
????????? ret
⊙ TObject.Create 的反匯編代碼:
?????????????????????????????????????? ; 函數(shù)進(jìn)入時(shí) eax = pointer to VMT??????????? (dl = 1)
??????????????????????????????????????????????????? eax = pointer to instance?????? (dl = 0)
?????????????????????????????????????? ; 函數(shù)返回時(shí) eax = pointer to instance
????????? test dl, dl????????????????? ; 檢查 dl 是否 = 0
????????? jz +$08????????????????????? ; dl = 0則跳至 @@1
????????? add esp, -$10??????????????? ; 增加 16 字節(jié)的堆棧,每次調(diào)用 _ClassCreate 之前都會(huì)進(jìn)行
?????????????????????????????????????? ; 用于 System._ClassCreate 設(shè)置結(jié)構(gòu)化異常處理
????????? call @ClassCreate??????????? ; 調(diào)用 System._ClassCreate
??????? @@1:
????????? test dl, dl????????????????? ; 檢查 dl 是否 = 0
????????? jz +$0f????????????????????? ; dl = 0則跳到 end 結(jié)束過程
????????? call @AfterConstruction????? ; dl <> 0 則調(diào)用 System._AfterConstruction
?????????????????????????????????????? ; (注意不是 TObject.AfterConstruction)
????????? pop dword ptr fs:[$00000000] ; fs:[0] 指向結(jié)構(gòu)化異常處理的函數(shù),此即取消最后一次的 try..except設(shè)置
?????????????????????????????????????? ; 這個(gè) try..except 在 System._ClassCreate 中創(chuàng)建
?????????????????????????????????????? ; 用于在出錯(cuò)時(shí)自動(dòng)恢復(fù)堆棧/釋放內(nèi)存分配/并調(diào)用 TObject.Free
????????? add esp, $0c???????????????? ; 恢復(fù)堆棧,注意只恢復(fù)了 12 字節(jié)的堆棧,還有4字節(jié)由上句 pop 了
????????? ret
注意:以上匯編代碼中重復(fù)出現(xiàn)了 test dl,dl,說明 Borland 并沒有特別對(duì)待 TObject.Create,TObject.Create確實(shí)是個(gè)空函數(shù)。TObject.Create的匯編代碼是由 constructor directive 指示編譯器形成的,編譯器對(duì)每個(gè)class 都一視同仁。
注意:這段 TObject.Create 代碼是在 PC 機(jī)上編譯的結(jié)果,嚴(yán)格地說應(yīng)該是在 Win32 操作系統(tǒng)上的實(shí)現(xiàn)之一。查看System._ClassCreate 就知道 Borland 還有其他的異常處理實(shí)現(xiàn)機(jī)制,產(chǎn)生的 TObject.Create 代碼也不相同。
⊙ System._AfterContruction 函數(shù)的代碼:
function _AfterConstruction(Instance: TObject): TObject;
begin
? Instance.AfterConstruction;
? Result := Instance;
end;
⊙ System._ClassCreate 函數(shù)的代碼:
function _ClassCreate(AClass: TClass; Alloc: Boolean): TObject;
asm
??????? { ->??? EAX = pointer to VMT????? }
??????? { <-??? EAX = pointer to instance }
??????? PUSH??? EDX???????????????????? ; 保存寄存器
??????? PUSH??? ECX
??????? PUSH??? EBX
??????? TEST??? DL,DL?????????????????? ; 如果 dl = 0 則不調(diào)用 TObject.NewInstance
??????? JL????? @@noAlloc
??????? CALL??? DWORD PTR [EAX] + VMTOFFSET TObject.NewInstance ; 調(diào)用 TObject.NewInstance
@@noAlloc:
{$IFNDEF PC_MAPPED_EXCEPTIONS}????????? ; 設(shè)置 PC 架構(gòu)的結(jié)構(gòu)化異常處理
??????? XOR???? EDX,EDX
??????? LEA???? ECX,[ESP+16]
??????? MOV???? EBX,FS:[EDX]
??????? MOV???? [ECX].TExcFrame.next,EBX
??????? MOV???? [ECX].TExcFrame.hEBP,EBP
??????? MOV???? [ECX].TExcFrame.desc,offset @desc
??????? MOV???? [ECX].TexcFrame.ConstructedObject,EAX?? { trick: remember copy to instance }
??????? MOV???? FS:[EDX],ECX
{$ENDIF}
??????? POP???? EBX???????????????????? ; 恢復(fù)寄存器
??????? POP???? ECX
??????? POP???? EDX
??????? RET
{$IFNDEF PC_MAPPED_EXCEPTIONS}????????? ; 設(shè)置非 PC 架構(gòu)的結(jié)構(gòu)化異常處理
@desc:
??????? JMP???? _HandleAnyException
? {?????? destroy the object????? }
??????? MOV???? EAX,[ESP+8+9*4]
??????? MOV???? EAX,[EAX].TExcFrame.ConstructedObject
??????? TEST??? EAX,EAX
??????? JE????? @@skip
??????? MOV???? ECX,[EAX]
??????? MOV???? DL,$81
??????? PUSH??? EAX
??????? CALL??? DWORD PTR [ECX] + VMTOFFSET TObject.Destroy
??????? POP???? EAX
??????? CALL??? _ClassDestroy
@@skip:
? {?????? reraise the exception?? }
??????? CALL??? _RaiseAgain
{$ENDIF}
end;
==============================================================
⊙ 測(cè)試目標(biāo):查看 constructor 函數(shù)中 inherited 的編譯器實(shí)現(xiàn)
==============================================================
⊙ 測(cè)試代碼及反匯編代碼:
type
? TMyClass = class(TObject)
??? constructor Create;
? end;
? constructor TMyClass.Create;
? begin
??? inherited; // 考查此句的實(shí)現(xiàn)
??? Beep;
? end;
procedure Test; register;
var
? Obj: TMyClass;
begin
? Obj := TMyClass.Create;
????????? mov dl, $01???????????????? ; class reference 時(shí)編譯器設(shè)置 dl = 1
????????? mov eax, [$004600ec]??????? ; 設(shè)置 eax 為指向 TMyClass 的 VMT pointer
????????? call TMyClass.Create??????? ; 調(diào)用 TMyClass.Create
????????? mov [ebp-$04], eax????????? ; 保存 新建對(duì)象的指針
end;
constructor TMyClass.Create 的反匯編代碼:
???????????????????????????????????????? ; 函數(shù)進(jìn)入時(shí) eax = pointer to VMT??????????? (dl = 1)
????????????????????????????????????????????????????? eax = pointer to instance?????? (dl = 0)
???????????????????????????????????????? ; 函數(shù)返回時(shí) eax = pointer to instance
begin
????????? push ebp?????????????????????? ; 這3句用于保存堆棧指針和創(chuàng)建堆棧
????????? mov ebp, esp
????????? add esp, -$08?????????????????
????????? test dl, dl??????????????????? ; 如果 dl = 0 則跳到 @ClassCreate 之后 @@1 處執(zhí)行
????????? jz +$08
????????? add esp, -$10????????????????? ; 為 _ClassCreate 調(diào)用準(zhǔn)備堆棧
????????? call @ClassCreate????????????? ; 調(diào)用 System._ClassCreate,執(zhí)行完成后 eax = 新建對(duì)象的指針
?????? @@1:
????????? mov [ebp-$05], dl????????????? ; 將 dl 值保存到堆棧中的 1 字節(jié)中,因?yàn)楹竺娴?inherited TObject.Create
???????????????????????????????????????? ; 可能會(huì)改變 edx 的值
????????? mov [ebp-$04], eax???????????? ; 保存 eax 到堆棧, eax = pointer to instance
inherited;
????????? xor edx, edx?????????????????? ; 將 edx 清零(dl = 0),以通知 TObject.Create 不用再調(diào)用
???????????????????????????????????????? ;? _ClassCreate 和 AfterConstructor (編譯器實(shí)現(xiàn))
????????? mov eax, [ebp-$04]???????????? ; 將 eax 的值還原為前面保存在堆棧的 eax 值
???????????????????????????????????????? ; (這句是多余的,但在其它情況下可能必須執(zhí)行此句)
????????? call TObject.Create??????????? ; 調(diào)用 TObject.Create
Beep;
????????? call Beep????????????????????? ; 繼承類中 inherited 之后實(shí)現(xiàn)的功能
????????? mov eax, [ebp-$04]???????????? ; 將 eax 的值還原為前面保存在堆棧的 eax 值
????????? cmp byte ptr [ebp-$05], $00??? ; (間接)檢查 dl 是否 = 0
????????? jz +$0f??????????????????????? ; dl = 0 則跳過 _AfterConstruction 到 @@2 處
????????? call @AfterConstruction??????? ; 調(diào)用 System._AfterConstruction
????????? pop dword ptr fs:[$00000000]?? ; 這2句恢復(fù)為 _ClassCreate 創(chuàng)建的堆棧空間
????????? add esp, $0c
?????? @@2:
????????? mov eax, [ebp-$04]???????????? ; 返回 pointer to instance
end;
????????? pop ecx
????????? pop ecx
????????? pop ebp
????????? ret
結(jié)論:真是精妙!一個(gè)對(duì)象的正常的創(chuàng)建(Obj := TMyObj.Create, 與后面不正常的調(diào)用相對(duì))過程是這樣的:
?? 1. 編譯器保證第一個(gè) constructor 調(diào)用之前 dl = 1
????? 編譯器保證 inherited Create? 調(diào)用之前 dl = 0
?? 2. dl = 1 時(shí) 編譯器保證 Create 時(shí) eax = pointer to class VMT
????? dl = 0 時(shí) 編譯器保證 Create 時(shí) eax = pointer to current object
?? 3. 編譯器保證任何層次的 constructor 調(diào)用后 eax = pointer to current object
?? 4. dl = 1 時(shí) 編譯器保證 Create 調(diào)用 System._ClassCreate,并與 constructor 相同的方式使用 eax
????? dl = 1 時(shí) 編譯器保證 Create 調(diào)用 System._AfterConstruction,并且調(diào)用前后 eax = pointer to current object
????? dl = 0 時(shí) 編譯器保證 Create 不會(huì)調(diào)用 System._ClassCreate
????? dl = 0 時(shí) 編譯器保證 Create 不會(huì)調(diào)用 System._AfterConstruction
?? 5. System._ClassCreate 中設(shè)置結(jié)構(gòu)化異常處理,在 Create 即將結(jié)束時(shí)關(guān)閉結(jié)構(gòu)化異常處理。
????? 如果出錯(cuò)則會(huì)(1)釋放由編譯器分配的內(nèi)存(2)恢復(fù)堆棧至創(chuàng)建對(duì)象之前(3)調(diào)用 TSomeClass.Destroy。
? 看上去有點(diǎn)繁雜,可是如果讀懂了上面 TObject.Create 和 TMyObject.Create 則會(huì)感覺對(duì)象的創(chuàng)建非常清晰。
?
==================================================================================
⊙ 測(cè)試目標(biāo):以 object reference 和 class reference 調(diào)用構(gòu)造函數(shù)的編譯器實(shí)現(xiàn)
==================================================================================
⊙ static constructor 測(cè)試代碼及反匯編代碼 (省略了begin 和 end 后面的堆棧分配代碼):
procedure Test; register;
var
? Obj: TObject;????????
begin
? Obj := TObject.Create;
????????? mov dl, $01?????????????? ; 采用 class reference 時(shí)編譯器自動(dòng)設(shè)置 dl = 1
????????? mov eax, [$004010a0]????? ; 把指向 TObject class VMT 的指針存入 eax,用于下一行調(diào)用
????????? call TObject.Create
????????? mov [ebp-$04], eax
? Obj := Obj.Create;
????????? or edx, -$01????????????? ; 采用 object reference 時(shí)編譯器自動(dòng)設(shè)置 edx 的所有 bit 都為 1
????????? mov eax, [ebp-$04]??????? ; 把 Obj 指針的所指的區(qū)域(即對(duì)象內(nèi)存空間)存入 eax,用于下一行調(diào)用
????????? call TObject.Create??????
????????? mov [ebp-$04], eax
end;
⊙ virtual constructor測(cè)試代碼及反匯編代碼 (省略了begin 和 end 后面的堆棧分配代碼):
procedure Test; register;
var
? Comp: TComponent;
begin
? Comp := TComponent.Create(nil);
????????? xor ecx, ecx??????????????????? ; 設(shè)置 參數(shù) = nil
????????? mov dl, $01???????????????????? ; 設(shè)置 dl = 1
????????? mov eax, [$00412eac]??????????? ; 設(shè)置 eax = class VMT pointer
????????? call TComponent.Create????????? ; 調(diào)用 TComponent.Create
????????? mov [ebp-$04], eax????????????? ; 保存 新建的對(duì)象至 Comp
? Comp := Comp.Create(nil);
????????? xor ecx, ecx??????????????????? ; 同上
????????? or edx, -$01??????????????????? ; 設(shè)置 edx 所有位為 1
????????? mov eax, [ebp-$04]????????????? ; 這句和下句 設(shè)置 ebx 為 TComponent class 的 VMT pointer
????????? mov ebx, [eax]????????????????? ; (如果 Comp 已經(jīng)實(shí)例化了,則 ebx 的值是對(duì)的)
????????? call dword ptr [ebx+$2c]??????? ; 可能是調(diào)用 TComponent.Create(Comp, -1, nil);
????????? mov [ebp-$04], eax????????????? ; 保存 新建的對(duì)象至 Comp
end;
結(jié)論:object reference 方式的 constructor 調(diào)用,編譯器嘗試實(shí)現(xiàn)為 inherited 調(diào)用,結(jié)果當(dāng)然是錯(cuò)誤。
=======================================================================
⊙ 測(cè)試目標(biāo):考查 Object 和 Class 在調(diào)用 class method 時(shí)的編譯器實(shí)現(xiàn)
=======================================================================
⊙ 測(cè)試代碼及反匯編代碼 (省略了begin 和 end 后面的堆棧分配代碼):
procedure Test; register;
var
? Com: TComponent;
? Str: String[255];
begin
? Com := TComponent.Create(nil);
????????? xor ecx, ecx
????????? mov dl, $01
????????? mov eax, [$00412eac]????????????? ; eax = pointer to class VMT
????????? call TComponent.Create???????????
????????? mov [ebp-$04], eax
? Str := Com.ClassName;
????????? lea edx, [ebp-$00000104]
????????? mov eax, [ebp-$04]??????????????? ; eax = pointer to object
????????? mov eax, [eax]??????????????????? ; eax = pointer to VMT
????????? call TObject.ClassName???????????
? Str := TComponent.ClassName;
????????? lea edx, [ebp-$00000104]????????? ; edx = address of Str
??????????????????????????????????????????? ; ShortString 類型的返回值是以 var 類型的參數(shù)傳遞的
????????? mov eax, [$00412eac]????????????? ; eax = pointer to class VMT
????????? call TObject.ClassName
end;
結(jié)論:class method 的調(diào)用隱含參數(shù) eax 為指向 VMT 的指針,不管是用 class 還是 object 方式調(diào)用,編譯器都會(huì)正確地把指向 class VMT 的指針傳遞給 eax。
========================================================================
⊙ 測(cè)試目標(biāo):考查 ShortString 返回值類型的函數(shù)沒有賦值時(shí)編譯器的實(shí)現(xiàn)
========================================================================
procedure Test; register;
begin
? TComponent.ClassName;
????????? lea edx, [ebp-$00000100]????? ; 編譯器會(huì)在堆棧中創(chuàng)建256 byte 的臨時(shí)空間,以保證 edx 不會(huì)為非法值
????????? mov eax, [$00412eac]?????????
????????? call TObject.ClassName
end;
⊙ TObject.ClassName 函數(shù)代碼:
class function TObject.ClassName: ShortString;
{$IFDEF PUREPASCAL}
begin
? Result := PShortString(PPointer(Integer(Self) + vmtClassName)^)^;
end;
{$ELSE}
asm
??????? { ->??? EAX VMT???????????????????????? }
??????? {?????? EDX Pointer to result string??? }
??????? PUSH??? ESI
??????? PUSH??? EDI
??????? MOV???? EDI,EDX???????????????? ; EDX 是返回值串的指針
??????? MOV???? ESI,[EAX].vmtClassName
??????? XOR???? ECX,ECX
??????? MOV???? CL,[ESI]??????????????? ; 設(shè)置 result string 的 length
??????? INC???? ECX
??????? REP???? MOVSB
??????? POP???? EDI
??????? POP???? ESI
end;
{$ENDIF}
結(jié)論:這只是我想了解字符串返回值的傳遞方式。
===================
?????? (完)
===================
?
來自:lance2000, 時(shí)間:2004-1-3 9:46:00, ID:2390276
寫的非常好!?
來自:dedema, 時(shí)間:2004-1-3 9:57:00, ID:2390302
這么厲害啊!一個(gè)晚上就完成了!?
來自:積步, 時(shí)間:2004-1-3 10:10:00, ID:2390324
mark?
來自:zhumoo, 時(shí)間:2004-1-3 10:26:00, ID:2390353
高手就是高手,學(xué)習(xí).?
來自:kk2000, 時(shí)間:2004-1-3 15:05:00, ID:2390894
樓主一個(gè)晚上完成,只能說佩服了!?
來自:renyi, 時(shí)間:2004-1-3 17:54:00, ID:2391175
厲害,不知 Delphi 的對(duì)象機(jī)制和 Java 、C# 相比,哪個(gè)個(gè)的效率更高??
來自:einsteingod, 時(shí)間:2004-1-3 19:07:00, ID:2391225
寫的非常好!?
來自:xff916, 時(shí)間:2004-1-3 19:49:00, ID:2391246
牛呀,學(xué)習(xí) ,學(xué)習(xí),在學(xué)習(xí)?
來自:積步, 時(shí)間:2004-1-3 19:50:00, ID:2391248
小弟還有一事不知。
通過asm訪問類 的私有變量.
?TA = class
? private
??? FA: Integer;
? public
??? procedure SetA(Value: Integer);
? end;
var
? Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
? A: TA;
? tmpInt: Integer;
begin
? A := TA.Create;
? tmpInt := 0;
? A.SetA(100);
? asm
??? MOV ECX, A.FA;??? //為什麼A.FA => 100 立即數(shù)就可以顯示為 100, 如果不改就顯示為其它值。
??? MOV tmpInt, ECX;
? end;
? ShowMessage(IntToStr(tmpInt));
? A.Free;
end;?
來自:savetime, 時(shí)間:2004-1-4 1:30:00, ID:2391511
to 積步:
DELPHI 對(duì)“MOV ECX, A.FA”生成的代碼實(shí)際上是以 Record 的類型生成的,這樣 ECX 的值就是
變量 A 的地址 加上 TA.FA 的偏移值,結(jié)果是 ECX 變成堆棧上的一個(gè)值,所以不對(duì)。
A.FA 的實(shí)際地址是 A 指向的地址(也就是對(duì)象內(nèi)存位置,而不是 A 的地址)加上 FA 相對(duì)于對(duì)象
頭部的偏移地址。我湊出以下的代碼,可以實(shí)現(xiàn)你要的結(jié)果:
var
?A: TA;
?tmpInt: Integer;
begin
?A := TA.Create;
?tmpInt := 0;
?A.SetA(100);
?asm
?? MOV EBX, A
?? MOV ECX, TA(EBX).FA;??? // 通知編譯器 EBX 指向的是 TA class
?? MOV tmpInt, ECX;
?end;
?ShowMessage(IntToStr(tmpInt));
?A.Free;
end;
我沒寫過匯編代碼,所以不知道 DELPHI 會(huì)不會(huì)自動(dòng)保護(hù)其他使用 EBX 的語句。如果你知道在混合
匯編的情況下如何使用寄存器請(qǐng)教我一下。
其實(shí)我真的不知道還有這種方法可以獲得私有成員,有趣!?
來自:savetime, 時(shí)間:2004-1-4 2:40:00, ID:2391532
內(nèi)容更正:
我發(fā)現(xiàn)自己在上文注釋中的一個(gè)錯(cuò)誤,在以下匯編第三行的“ push ecx ”我把它注釋為
? “保存 ecx (無用的語句)”,
更正為:
? “分配局部變量 Obj 的堆棧空間”。
原來 add esp, -$4? 花費(fèi) 3 個(gè)字節(jié)的指令
而?? push ecx????? 只要 1 個(gè)字節(jié)的指令,執(zhí)行更快
大家現(xiàn)在知道我不是高手了吧,我是一邊翻匯編手冊(cè),一邊寫注釋的。我只能毫不謙虛地
說:我真的是初學(xué)者。高手看這些簡單的匯編代碼哪里需要花一個(gè)晚上。
希望大家關(guān)注內(nèi)容,不要只是說“好”,重要的是“有沒有錯(cuò)誤”,這樣才能提高。
出錯(cuò)的注釋段:
=================================================
⊙ 測(cè)試目標(biāo):查看 TObject.Create 的編譯器實(shí)現(xiàn)
=================================================
⊙ 測(cè)試代碼及反匯編代碼:
procedure Test; register;
var
?Obj: TObject;????????
begin
???????? push ebp???????????????????? ; 前2句用于設(shè)置堆棧指針
???????? mov ebp, esp
???????? push ecx???????????????????? ; 保存 ecx (無用的語句) -> (更正為) 分配局部變量 Obj 的堆棧空間
?Obj := TObject.Create;
???????? mov dl, $01????????????????? ; 設(shè)置 dl = 1,通知 TObject.Create 這是一次新建對(duì)象的調(diào)用
???????? mov eax, [$004010a0]???????? ; 把指向 TObject class VMT 的指針存入 eax,
????????????????????????????????????? ; 作為 TObject.Create 隱含的 Self 參數(shù)
???????? call TObject.Create????????? ; 調(diào)用 TObject.Create 函數(shù)
???????? mov [ebp-$04], eax?????????? ; TObject.Create 返回新建對(duì)象的指針至 Obj
end;
???????? pop ecx????????????????????? ; 恢復(fù)堆棧并返回
???????? pop ebp
???????? ret
?
來自:savetime, 時(shí)間:2004-1-4 2:40:00, ID:2391533
to 積步,
我測(cè)試了在混合匯編的情況下修改寄存器時(shí)的實(shí)現(xiàn),結(jié)果是:Delphi 會(huì)自動(dòng)把嵌入?yún)R編中修改了的寄存器備份在堆
棧中,所以可以隨意使用 Delphi 允許的寄存器。?
來自:book523, 時(shí)間:2004-1-4 10:20:00, ID:2391614
結(jié)論:真是精妙!一個(gè)對(duì)象的正常的創(chuàng)建(Obj := TMyObj.Create, 與后面不正常的調(diào)用相對(duì))過程是這樣的:
? 1. 編譯器保證第一個(gè) constructor 調(diào)用之前 dl = 1
???? 編譯器保證 inherited Create? 調(diào)用之前 dl = 0
? 2. dl = 1 時(shí) 編譯器保證 Create 時(shí) eax = pointer to class VMT
???? dl = 0 時(shí) 編譯器保證 Create 時(shí) eax = pointer to current object
? 3. 編譯器保證任何層次的 constructor 調(diào)用后 eax = pointer to current object
? 4. dl = 1 時(shí) 編譯器保證 Create 調(diào)用 System._ClassCreate,并與 constructor 相同的方式使用 eax
???? dl = 1 時(shí) 編譯器保證 Create 調(diào)用 System._AfterConstruction,并且調(diào)用前后 eax = pointer to current object
???? dl = 0 時(shí) 編譯器保證 Create 不會(huì)調(diào)用 System._ClassCreate
???? dl = 0 時(shí) 編譯器保證 Create 不會(huì)調(diào)用 System._AfterConstruction
? 5. System._ClassCreate 中設(shè)置結(jié)構(gòu)化異常處理,在 Create 即將結(jié)束時(shí)關(guān)閉結(jié)構(gòu)化異常處理。
???? 如果出錯(cuò)則會(huì)(1)釋放由編譯器分配的內(nèi)存(2)恢復(fù)堆棧至創(chuàng)建對(duì)象之前(3)調(diào)用 TSomeClass.Destroy。
?看上去有點(diǎn)繁雜,可是如果讀懂了上面 TObject.Create 和 TMyObject.Create 則會(huì)感覺對(duì)象的創(chuàng)建非常清晰。
-------------------------------------------------------------------------------
為什么要保存經(jīng)常調(diào)用dl的值?
Dl主要用來表示是class級(jí)別調(diào)用還是對(duì)象級(jí)別調(diào)用,
class級(jí)別調(diào)用時(shí),constructor會(huì)自動(dòng)執(zhí)行
System._ClassCreate,NewInstance,InitInstance,AfterConstruction等過程,
然后才是Constructor中的代碼,
而對(duì)象級(jí)別調(diào)用時(shí),只會(huì)執(zhí)行Constructor中的代碼。?
來自:savetime, 時(shí)間:2004-1-4 12:18:00, ID:2391785
to book523,
你說的是我在“⊙測(cè)試目標(biāo):以 object reference 和 class reference 調(diào)用構(gòu)造函數(shù)的編譯器實(shí)現(xiàn)”中的結(jié)果吧。我在文章中寫的結(jié)論是:“object reference 方式的 constructor 調(diào)用,編譯器嘗試實(shí)現(xiàn)為 inherited 調(diào)用”
其實(shí)如果你看了測(cè)試代碼的反匯編過程,就應(yīng)該知道我的這個(gè)結(jié)論是錯(cuò)誤的。Borland 在 Object Pascal Reference 中寫的就是你說的“對(duì)象級(jí)別調(diào)用時(shí),只會(huì)執(zhí)行Constructor中的代碼。”,可是事實(shí)上不是這樣。
在形式如下的代碼中
? AComp := AComp.Create
Borland 先將 edx 所有 bit 設(shè)置為 1 ,也就是 dl 為 1,也就是仍然嘗試沿用 class 級(jí)別的調(diào)用。可是你看 Delphi 的生成的匯編代碼不知所云,所以根本就沒有所謂的“對(duì)象級(jí)別調(diào)用”。我認(rèn)為 Borland 應(yīng)該對(duì) AComp := AComp.Create 調(diào)用提示為語法錯(cuò)誤。我實(shí)在是想不到什么時(shí)候會(huì)需要這種形式的調(diào)用。
我測(cè)試這樣的調(diào)用方式是因?yàn)槲医?jīng)常在創(chuàng)建 Form 時(shí)忘了寫 T 這一標(biāo)識(shí)符:
? ChildForm := ChildForm.Create(nil); // 這里應(yīng)該是 TChildForm.Create(nil)
我想知道這樣的結(jié)果是什么。
?
來自:vc_delphi, 時(shí)間:2004-1-4 18:27:00, ID:2392395
高手就是高手,學(xué)習(xí).?
來自:積步, 時(shí)間:2004-1-4 21:30:00, ID:2392563
to? savetime:
多謝指教。
以前也研究過匯編什麼東東的, 但是現(xiàn)在沒有多少時(shí)間研究。?
來自:積步, 時(shí)間:2004-1-4 21:35:00, ID:2392623
procedure TForm1.Button1Click(Sender: TObject);
var
? A: TA;
? tmpInt: Integer;
begin
? A := TA.Create;
? tmpInt := 0;
? A.SetA(100);
? asm
??? MOV EAX, A;
??? MOV EAX, [EAX + 4];
??? //這樣也行 MOV EAX, [EAX] + 4 都是得到當(dāng)前對(duì)象的第一變量
??? //MOV EAX, [EAX] + 8 訪問第二個(gè)變量, 依此類推??
??? //MOV EAX, [EAX], 是指向VMT的指針
?? MOV tmpInt, EAX;
? end;
? ShowMessage(IntToStr(tmpInt));
? A.Free;
end;
這樣也可以實(shí)現(xiàn), 但是想不到savetime兄弟還有更高的招術(shù), 厲害厲害!!?
來自:xchen.d, 時(shí)間:2004-1-4 21:33:00, ID:2392650
我看‘中國有救了‘,真有耐力!呵呵
心不靜,做不了?
來自:savetime, 時(shí)間:2004-1-5 10:15:00, ID:2392767
to 積步,
? 我又從你這里學(xué)到一點(diǎn)匯編知識(shí),知識(shí)就是這樣積累出來的呀。
to everybody,
其實(shí)要看懂文中的匯編并不難,我做的這件工作只是 Delphi 的基礎(chǔ)而已。讀懂和會(huì)用根本上是兩回事。
我現(xiàn)在正在分析 TWinControl 如何如何封裝 Windows 的消息系統(tǒng),二天過去,進(jìn)展不大。相關(guān)的函數(shù)太多了,還有一些匯編夾在其中。我認(rèn)為消息系統(tǒng)才是 VCL 的關(guān)鍵地方。整個(gè)程序的執(zhí)行過程全在里面。?
來自:tingliuxingyu, 時(shí)間:2004-1-5 10:56:00, ID:2393353
高手啊,高手,想法啊,想法,學(xué)習(xí)啊,學(xué)習(xí)?
來自:book523, 時(shí)間:2004-1-5 11:35:00, ID:2393464
我認(rèn)為 Borland 應(yīng)該對(duì) AComp := AComp.Create 調(diào)用提示為語法錯(cuò)誤。我實(shí)在是想不到什么時(shí)候會(huì)需要這種形式的調(diào)用。
--------------------------------------------------------------------------------
看看TApplication的CreateForm過程,有一個(gè)典型的
AComp := AComp.Create 例子,有些時(shí)候生成的匯編是不知所云,
那是因?yàn)槲覀儗?duì)匯編根本就不了解,匯編的語句單個(gè)拿出來都很好懂,
但是要一段拿出來要明白要實(shí)現(xiàn)什么功能就困難了。
procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
var
? Instance: TComponent;
begin
? Instance := TComponent(InstanceClass.NewInstance); //調(diào)用NewInstance方法分配內(nèi)存,創(chuàng)建框架等等。然后根據(jù)TComponent類圈定框架賦給Instance
? TComponent(Reference) := Instance;//給Reference變量賦值
? try
??? Instance.Create(Self);//執(zhí)行constructor Create(Owner) 的代碼
? except
??? TComponent(Reference) := nil;
??? raise;
? end;
? if (FMainForm = nil) and (Instance is TForm) then
? begin
??? TForm(Instance).HandleNeeded; //調(diào)用HandleNeeded過程,創(chuàng)建窗體。
??? FMainForm := TForm(Instance);//把第一個(gè)創(chuàng)建的窗體作為主窗體。
? end;
end;?
來自:savetime, 時(shí)間:2004-1-5 11:53:00, ID:2393534
to book523,
我又錯(cuò)了,你真的找出了這樣的代碼,厲害!我要考慮一下再回復(fù)你。?
來自:book523, 時(shí)間:2004-1-5 12:41:00, ID:2393636
?? 我現(xiàn)在正在分析 TWinControl 如何如何封裝 Windows 的消息系統(tǒng),二天過去,進(jìn)展不大。相關(guān)的函數(shù)太多了,還有一些匯編夾在其中。我認(rèn)為消息系統(tǒng)才是 VCL 的關(guān)鍵地方。整個(gè)程序的執(zhí)行過程全在里面。
---------------------------------------
?? 我現(xiàn)在也在做這個(gè)工作,昨天看了李維的那本新書《inside vcl》開始有點(diǎn)頭緒了,
你不要從TWinControl開始,那樣會(huì)不知所云,你可以從項(xiàng)目工程文件的那三句話開始
分析,估計(jì)很快就能理出個(gè)大概來。
? Application.Initialize;
? Application.CreateForm(TForm1, Form1);
? Application.Run;
?
來自:book523, 時(shí)間:2004-1-5 12:45:00, ID:2393645
我又錯(cuò)了,你真的找出了這樣的代碼,厲害!我要考慮一下再回復(fù)你。
---------------------------------------------------------------
正好昨天分析這個(gè)過程看到的。?
來自:zeroyou, 時(shí)間:2004-1-5 13:41:00, ID:2393797
到此一游,?
來自:savetime, 時(shí)間:2004-1-5 13:50:00, ID:2393802
to book523,
我找到錯(cuò)誤的的原因了。我把匯編代碼 jz 和 jl 搞混了,所以上面注釋 System._ClassCreate 有錯(cuò)誤。
其實(shí) dl 寄存器有 3 種狀態(tài)(我原來以為是 2 種):
dl = 1? 是在 TClass.Create 之前被設(shè)置
dl = 0? 是在 Inherited???? 之前被設(shè)置
dl = -1 是在 Object.Create 之前被設(shè)置
dl = 1? System._ClassCreate 被調(diào)用
dl = 0? System._ClassCreate 不被調(diào)用
dl = -1 System._ClassCreate 被調(diào)用, 但不執(zhí)行 NewInstance 工作,只是設(shè)置異常斷點(diǎn)
? “⊙測(cè)試目標(biāo):以 object reference 和 class reference 調(diào)用構(gòu)造函數(shù)的編譯器實(shí)現(xiàn)” —— 的注釋也有錯(cuò)誤。
重讀 Obj.Create 的匯編代碼,終于明白了應(yīng)該是有 Object Reference consturction 這一事實(shí)。只是在 Obj.Create 之前必須用 NewInstance 分配內(nèi)存和設(shè)置 Obj VMT 的指針,否則 Obj.Create 就會(huì)失敗。Obj.Create 不會(huì)調(diào)用 NewInstance,但會(huì)設(shè)置異常處理,保證出錯(cuò)時(shí)析構(gòu)函數(shù)被調(diào)用。
====================================
對(duì)于 VCL 的消息系統(tǒng),我原本也想從上向下讀,不過我對(duì) TWinControl 沒什么了解,只好先看它都包裝了哪些函數(shù)。我現(xiàn)在可以用你推薦的方法試試。
我猜想 TApplication 只是調(diào)用 GetMessage 再 DispatchMessage,所以關(guān)鍵的是 VCL 如何注冊(cè) WndProc,并把注冊(cè)的這個(gè) WndProc 關(guān)聯(lián)到對(duì)象上。
MakeObjectInstance 很有意思,在內(nèi)存中建立一塊一塊的 ObjectInstance 代碼,ObjectInstance 的地址又被注冊(cè)為標(biāo)準(zhǔn)的 Windows Procedure。
我是從 TWinControl.Create 入手的,第一句不太明白,
? FObjectInstance := Classes.MakeObjectInstance(MainWndProc);
? { function MakeObjectInstance(Method: TWndMethod): Pointer }
你能解釋一下 MakeObjectInstance 的 MainWndProc 傳遞的實(shí)際內(nèi)容是什么(是不是傳遞 MainWndProc 的指針和 Self 指針?我是不太明白為什么編譯器知道要把 Self 傳過去。)
====================================
你已經(jīng)拿到 Inside VCL 了,幸福啊。我們這里的書店太差了,到今天還沒通知我到貨。?
來自:baifeng, 時(shí)間:2004-1-5 13:52:00, ID:2393836
g z?
來自:book523, 時(shí)間:2004-1-5 14:26:00, ID:2393903
你能解釋一下 MakeObjectInstance 的 MainWndProc 傳遞的實(shí)際內(nèi)容是什么(是不是傳遞 MainWndProc 的指針
和 Self 指針?我是不太明白為什么編譯器知道要把 Self 傳過去。)
============================================================================
傳遞的是MainWndProc 的指針和 Self 指針,看delphi的消息結(jié)構(gòu):
? TMessage = packed record
??? Msg: Cardinal;
??? case Integer of
????? 0: (
??????? WParam: Longint;
??????? LParam: Longint;
??????? Result: Longint);
????? 1: (
??????? WParamLo: Word;
??????? WParamHi: Word;
??????? LParamLo: Word;
??????? LParamHi: Word;
??????? ResultLo: Word;
??????? ResultHi: Word);
? end;
而windows中的消息結(jié)構(gòu):
typedef struct tagMSG
{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
顯然少了一個(gè)最重要的field即窗口句柄hwnd。
因此在delphi中處理消息的方法都會(huì)把self作為隱含參數(shù),
把self壓入到EAX中,再把TMessage 結(jié)構(gòu)的指針作為第二個(gè)參數(shù),放入EDX中,
這樣才符合windows的回調(diào)函數(shù)的格式,
實(shí)際上MainWndProc 正是delphi窗體類的回調(diào)函數(shù)。
?
來自:book523, 時(shí)間:2004-1-5 14:32:00, ID:2393915
?你上面對(duì)Create過程的跟蹤分析真是精辟啊,令人佩服啊。
========================================================
你已經(jīng)拿到 Inside VCL 了,幸福啊。我們這里的書店太差了,到今天還沒通知我到貨。
===============================
我是在dearbook上訂了,12。31號(hào)訂的,2。2號(hào)就拿到了。
?
來自:book523, 時(shí)間:2004-1-5 14:39:00, ID:2393940
to savetime:
看到了帖子嗎:
http://www.delphibbs.com/delphibbs/dispq.asp?lid=584889
我從收藏夾中提上來的。?
來自:savetime, 時(shí)間:2004-1-5 16:24:00, ID:2394288
看到,正在研究。?
來自:WoDing, 時(shí)間:2004-1-5 21:24:00, ID:2394955
留下記號(hào),到此一游?
來自:kk2000, 時(shí)間:2004-1-6 9:45:00, ID:2395470
? TO:savetime 、積步 我也正在學(xué)習(xí)匯編。其實(shí)我也有savetime的想法。可是自己能力
和時(shí)間的問題。只能向你們學(xué)習(xí)了。關(guān)于:積步兄的問題確實(shí)有趣!而且 savetime兄的回答就精妙了! 現(xiàn)在我調(diào)試一下結(jié)果是下面的:
1.這是(savetime)做的:MOV ECX, TA(EBX).FA;??? // 通知編譯器 EBX 指向的是 TA class
其實(shí)這句話可以這樣翻譯過來(這是我的個(gè)人看法,有步不的地方請(qǐng)指正):
asm
??? MOV EAX, [A];//這句是把這個(gè)實(shí)例的引用傳進(jìn)寄存器EAX,也就是堆中的首地址
??? //MOV EAX,TA(EAX).FA;
??? MOV tmpInt,EAX ;//這里是把寄存器中的值(也就是實(shí)例的首地址,而不是變量的地址)傳給tempInt;
? end;
//在這里也就是相當(dāng)于(savetime)的那句話了
? ShowMessage(IntToStr(TA(tmpInt).FA));
2.這是(積步)提問的:
procedure TForm1.Button1Click(Sender: TObject);
var
?A: TA;
?tmpInt: Integer;
begin
?A := TA.Create;
?tmpInt := 0;
?A.SetA(100);
?asm
?? MOV ECX, A;??? //為什麼A.FA => 100 立即數(shù)就可以顯示為 100, 如果不改就顯示為其它值。(這里修改一下)
?? MOV tmpInt, ECX;
?end;
?ShowMessage(IntToStr(TA(tmpInt).FA));//主意這里如果改為這樣就對(duì)了,這里可能是編譯器做的
?A.Free;
end;
其實(shí)(積步)的做法就是得到這個(gè)類的段地址的FA的偏移地址。而不是該地址
里面的內(nèi)容,至于怎么樣取出我們要找到該變量地址的值,就象(savetime)那樣
做!但是那樣那做為什么能夠取出他的值?后來(積步)用寄存器加上立即數(shù)也可以
取出來,我還是模糊???
?
?
來自:savetime, 時(shí)間:2004-1-6 10:19:00, ID:2395616
to kk2000:
你的問題很簡單
?? MOV EAX, A;? // A 是指向?qū)ο蟮闹羔?#xff0c;這句把對(duì)象在內(nèi)存中的地址存入 EAX
?????????????????? 這時(shí) EAX + 4 就是對(duì)象的第一個(gè)成員變量
?????????????????? TA(EAX).FA 就是 EAX 加上 FA 的偏移處的內(nèi)容
?????????????????? 這是 Delphi 語法支持的,能大概看得懂就行了。
你可是解決了一個(gè)大問題,不用匯編也能訪問私有成員,我原以為是匯編的特權(quán)呢。
procedure TForm1.Button1Click(Sender: TObject);
var
? A: TA;
begin
? A := TA.Create;
? A.SetA(100);
? ShowMessage(IntToStr(TA(A).FA));
? A.Free;
end;
?
來自:savetime, 時(shí)間:2004-1-6 10:28:00, ID:2395657
我錯(cuò)了,我忘記同一個(gè)單元的類可以互相訪問私有成員,真是該死。?
來自:chnplzh, 時(shí)間:2004-1-6 10:40:00, ID:2395707
收藏!?
來自:book523, 時(shí)間:2004-1-6 11:47:00, ID:2395949
to savetime:
?? 你做什么工作啊,好像挺有時(shí)間的啊。
有時(shí)間到www.01cn.net上看看,是個(gè)好地方啊。?
來自:savetime, 時(shí)間:2004-1-6 12:05:00, ID:2396007
to book523,
www.01cn.net 真是高手云集啊,收藏!
我在公司做雜務(wù),主要是收發(fā)貨、檢查客戶退回來的產(chǎn)品。我總是盡快把雜務(wù)處理掉,其他的時(shí)間就自己寫點(diǎn)小程序,讀讀書。我也想去軟件公司,可是沒有人要我,學(xué)歷太低,專業(yè)知識(shí)也不夠。只好先混著。?
來自:ka52, 時(shí)間:2004-1-6 12:43:00, ID:2396097
看了看.感覺這才是真正的高手之路 .
可偶對(duì)匯編太沒感覺了..我開始看看vcl吧 :)?
來自:Walnut_Tom, 時(shí)間:2004-1-6 13:52:00, ID:2396282
這里也是高手如云呀?
來自:book523, 時(shí)間:2004-1-6 14:03:00, ID:2396310
01cn上很多都是在這里已成名的高手。
當(dāng)然現(xiàn)在人氣還不夠。?
來自:savetime, 時(shí)間:2004-1-6 16:08:00, ID:2396594
昨天只看完了 Windows Callback FObjectInstance 方法的匯編代碼。如果 Borland 把 TMessage 設(shè)計(jì)為 Result 字段在最前面,就不用把 Windows 回調(diào)前在堆棧中建立的參數(shù)再 PUSH 一遍了,直接把 Windows 傳過來的 HWND 設(shè)置為 0 后當(dāng)作 Result 用就行了(因?yàn)镸ainWndProc 并沒有使用到 HWND)。這樣的話 StdWndProc 就可以設(shè)計(jì)為只有一段小段代碼而不是函數(shù),處理消息的效率可以稍微提高一點(diǎn)。看來 Borland 寧愿降低效率也不愿放棄語法的美感。
下面是從 Windows Callback 開始到 TWinControl.MainWndProc 被調(diào)用的匯編代碼:
DispatchMessage(&Msg)??? // Windows 準(zhǔn)備回調(diào)
Windows 準(zhǔn)備回調(diào) TWinControl.FObjectInstance:
??????????? push LPARAM
??????????? push WPARAM
??????????? push UINT
??????????? push HWND
??????????? push (eip.Next)????? ; 把Windows Callback后下一條語句的地址保存在堆棧中
??????????? jmp FObjectInstance.Code
FObjectInstance.Code 只有一條 call 語句:
call ObjectInstance.offset??
??????????? push eip.Next
??????????? jmp InstanceBlock.Code?? ;調(diào)用 InstanceBlock.Code
InstanceBlock.Code:
??????????? pop ecx?????????????? ;將 eip.Next 的值存入 ecx, 用于取 @MainWndProc 和 Self
??????????? jmp StdWndProc??????? ;跳轉(zhuǎn)至 StdWndProc
StdWndProc 的反匯編代碼:
function StdWndProc(Window: HWND; Message, WParam: Longint; LParam: Longint): Longint; stdcall; assembler;
asm
??????????? push ebp
??????????? mov ebp, esp
??????? XOR???? EAX,EAX
??????????? xor eax, eax
??????? PUSH??? EAX
??????????? push eax????????????????????? ; TMessage.Result := 0
??????? PUSH??? LParam
??????????? push dword ptr [ebp+$14]
??????? PUSH??? WParam
??????????? push dword ptr [ebp+$10]
??????? PUSH??? Message
??????????? push dword ptr [ebp+$0c]
??????? MOV???? EDX,ESP
??????????? mov edx, esp????????????????? ; mov edx, var TMessage
??????? MOV???? EAX,[ECX].Longint[4]
??????????? mov eax, [ecx+$04]??????????? ; mov eax, Self
??????? CALL??? [ECX].Pointer
??????????? call dword ptr [ecx]????????? : call MainWndProc
??????? ADD???? ESP,12
??????????? add esp, $0c
??????? POP???? EAX
??????????? pop eax
end;
??????????? pop ebp
??????????? ret $0010
??????????? mov eax, eax
對(duì)于 VCL 消息系統(tǒng)的學(xué)習(xí),這只是個(gè)開始。在 TWinControl.CreateWnd 方法中設(shè)置斷點(diǎn),查看什么時(shí)候建立窗口,結(jié)果看到的調(diào)用堆棧是下面這樣:
TWinControl.CreateWnd
TScrollingWinControl.CreateWnd
TCustomForm.CreateWnd
TWinControl.CreateHandle
TWinControl.HandleNeeded
TWinControl.GetHandle
TWinControl.GetDeviceContext(0)
TControlCanvas.CreateHandle
TCanvas.RequiredState([csHandleValid..csFontValid])
TCanvas.TextExtent('0')
TCanvas.TextHeight('0')
TCustomForm.GetTextHeight
TCustomForm.ReadState($9525B4)
TReader.ReadRootComponent($951FA8)
TStream.ReadComponent($951FA8)
InternalReadComponentRes('TForm1',4194304,$951FA8)
InitComponent(TForm1)
InitInheritedComponent($951FA8,TForm)
TCustomForm.Create($9517C8)
TApplication.CreateForm(TForm1,(no value))
Project1
真是太復(fù)雜了,除了硬著頭皮一行行代碼讀下去,還有什么好辦法呢?
?
來自:savetime, 時(shí)間:2004-1-6 17:55:00, ID:2396907
瀏覽了一個(gè) Application 的執(zhí)行過程,把過程簡單描述一下:
begin
首先調(diào)用了一個(gè)隱含的過程 _InitExe
_InitExe 初始化了 Module 信息,然后調(diào)用 _StartExe
_StartExe 設(shè)置異常等,然后調(diào)用 InitUnits
InitUnits 調(diào)用各個(gè) Units 的 Initialization 段
其中調(diào)用到了 Controls.pas 的 Initializaiotn 段
Controls.Initialization 調(diào)用 InitControls
InitControls 主要建立 Mouse, Screen和 Application 實(shí)例
Application.Create 調(diào)用 Application.CreateHandle
Application.CreateHandle 建立一個(gè)窗口,并設(shè)置 Application.WndProc 為回調(diào)函數(shù)
Application.WndProc 主要處理一些應(yīng)用程序級(jí)別的消息
然后才是 Project 的第一句: Application.Initialize;
這個(gè)過程基本上沒有內(nèi)容,主要是讓用戶設(shè)置一個(gè)初始化函數(shù)
然后是 Project 的第二句: Application.CreateForm(TForm1, Form1);
新增 Form1的內(nèi)存實(shí)例
調(diào)用 Form1.Create? -> TCustomForm.Create(Self)
TCustomForm.Create 調(diào)用 InitInheritedComponent
InitInheritedComponent 調(diào)用 InternalReadComponentRes
InternalReadComponentRes 調(diào)用 TStream.ReadComponent
TReader.ReadRootComponent 調(diào)用 TCustomForm.ReadState
TCustomForm.ReadState 調(diào)用到了 GetTextHeight
TCustomForm.GetTextHeight 調(diào)用 TCanvas.TextHeight
TCanvas.TextExtent 調(diào)用 TCanvas.RequiredState
這時(shí)候才標(biāo)識(shí)出 TControlCanvas.CreateHandle
TControlCanvas.CreateHandle 又調(diào)用了 TWinControl.GetDeviceContext
TWinControl.GetDeviceContext 要求使用 Handle 于是調(diào)用 TWinControl.GetHandle
TWinControl.GetHandle 需要HWnd,于是調(diào)用TWinControl.HandleNeeded
由于沒有建立 HWnd,于是調(diào)用 TWinControl.CreateHandle
TWinControl.CreateHandle 這才調(diào)用 TCustomForm.CreateWnd 建立窗口
TCustomForm.CreateWnd 調(diào)用 TScrollingWinControl.CreateWnd
TScrollingWinControl.CreateWnd 調(diào)用 TWinControl.CreateWnd這時(shí)才真正建立了一個(gè)窗口
真是漫漫長路。
最后是:Application.Run;
我還沒看代碼,估計(jì)是建立消息循環(huán)之類。
看來不能這樣跟蹤,相關(guān)流操作太多,今天回家單獨(dú)建立一個(gè) TForm 跟蹤一下。
下班了!?
來自:xzgyb, 時(shí)間:2004-1-7 21:02:00, ID:2399360
最近在看<<windows程序設(shè)計(jì)>>,對(duì)windows編程稍懂了點(diǎn),下午照vcl抄了一下,只是主要的窗口建立部分,不知對(duì)你有沒有用,如下
unit MyWindowUnit;
interface
uses Windows, SysUtils, Messages;
type
? TMyCreateParams = record
??? Caption: PChar;
??? Style: DWORD;
??? ExStyle: DWORD;
??? X, Y: Integer;
??? Width, Height: Integer;
??? WndParent: HWnd;
??? Param: Pointer;
??? WindowClass: TWndClass;
??? WinClassName: array[0..63] of Char;
? end;
? TMyMessage = packed record
??? Msg: Cardinal;
??? case Integer of
????? 0: (
??????? WParam: Longint;
??????? LParam: Longint;
??????? Result: Longint);
????? 1: (
??????? WParamLo: Word;
??????? WParamHi: Word;
??????? LParamLo: Word;
??????? LParamHi: Word;
??????? ResultLo: Word;
??????? ResultHi: Word);
? end;
? TMyWndMethod = procedure(var Message: TMyMessage) of object;
? TMyWindow = class
? private
??? FHandle: HWnd;
??? FDefWndProc: Pointer;
??? FObjectInstance: Pointer;
??? function GetHandle: HWnd;
? protected
??? procedure CreateWindowHandle(const Params: TMyCreateParams); virtual;
??? procedure CreateParams(var Params: TMyCreateParams); virtual;
??? procedure CreateHandle; virtual;
??? procedure CreateWnd; virtual;
??? procedure WndProc(var Message: TMyMessage); virtual;
??? procedure MainWndProc(var Message: TMyMessage);
? public
??? procedure DefaultHandler(var Message); override;
??? procedure HandleNeeded;
??? procedure ShowWindow;
??? procedure UpdateWindow;
??? constructor Create; virtual;
??? property Handle: HWnd read GetHandle;
? end;
implementation
{ TMyWindow }
type
? PMyObjectInstance = ^TMyObjectInstance;
? TMyObjectInstance = packed record
??? CodeCall: Byte;
??? Offset: Integer;
??? Method: TMyWndMethod;
??? CodeJmp: array[1..2] of Byte;
??? WndProcPtr: Pointer;
? end;
function MyStdWndProc(Window: HWND; Message, WParam: Longint;
? LParam: Longint): Longint; stdcall; assembler;
asm
? XOR???? EAX,EAX
? PUSH??? EAX
? PUSH??? LParam
? PUSH??? WParam
? PUSH??? Message
? MOV???? EDX,ESP
? MOV???? EAX,[ECX].Longint[4]
? CALL??? [ECX].Pointer
? ADD???? ESP,12
? POP???? EAX
end;
function MyCalcJmpOffset(Src, Dest: Pointer): Longint;
begin
? Result := Longint(Dest) - (Longint(Src) + 5);
end;
function MyMakeObjectInstance(Method: TMyWndMethod): Pointer;
const
? BlockCode: array[1..2] of Byte = (
??? $59,?????? { POP ECX }
??? $E9);????? { JMP MyStdWndProc }
var
? PBlock: PMyObjectInstance;
begin
? PBlock := VirtualAlloc(nil, SizeOf(TMyObjectInstance), MEM_COMMIT,
???????????????????????? PAGE_EXECUTE_READWRITE);
? Move(BlockCode, PBlock^.CodeJmp, SizeOf(BlockCode));
? PBlock^.WndProcPtr := Pointer(MyCalcJmpOffset(@PBlock^.CodeJmp[2], @MyStdWndProc));
? PBlock^.CodeCall := $E8;
? PBlock^.Offset := MyCalcJmpOffset(PBlock, @PBlock^.CodeJmp);
? PBlock^.Method := Method;
? Result := PBlock;
end;
constructor TMyWindow.Create;
begin
? FObjectInstance := MyMakeObjectInstance(MainWndProc);
end;
procedure TMyWindow.CreateHandle;
begin
? if FHandle = 0 then CreateWnd;
end;
procedure TMyWindow.CreateParams(var Params: TMyCreateParams);
begin
? FillChar(Params, SizeOf(Params), 0);
? with Params do
? begin
??? Style := WS_OVERLAPPEDWINDOW;
??? WndParent := 0;
??? WindowClass.style := CS_VREDRAW + CS_HREDRAW + CS_DBLCLKS;
??? WindowClass.lpfnWndProc := @DefWindowProc;
??? WindowClass.hCursor := LoadCursor(0, IDC_ARROW);
??? WindowClass.hbrBackground := COLOR_3DFACE + 1;
??? WindowClass.hIcon := LoadIcon(0, IDI_APPLICATION);
??? WindowClass.hInstance := HInstance;
??? StrPCopy(WinClassName, Self.ClassName);
? end;
end;
procedure TMyWindow.CreateWindowHandle(const Params: TMyCreateParams);
begin
? with Params do
??? FHandle := CreateWindow(WinClassName, Caption, Style,
??????????????????????????? X, Y,
??????????????????????????? Width, Height,
??????????????????????????? WndParent, 0, WindowClass.hInstance, Param);
end;
var
? MyCreationControl: TMyWindow;
function MyInitWndProc(HWindow: HWnd; Message, WParam,
? LParam: Longint): Longint; stdcall;
begin
? MyCreationControl.FHandle := HWindow;
?? SetWindowLong(HWindow, GWL_WNDPROC,
??? LongInt(MyCreationControl.FObjectInstance));
? asm
??????? PUSH??? LParam
??????? PUSH??? WParam
??????? PUSH??? Message
??????? PUSH??? HWindow
??????? MOV???? EAX,MyCreationControl
??????? MOV???? MyCreationControl,0
??????? CALL??? [EAX].TMyWindow.FObjectInstance
??????? MOV???? Result,EAX
? end;
end;
procedure TMyWindow.CreateWnd;
var
? Params: TMyCreateParams;
? TempClass: TWndClass;
? ClassRegistered: Boolean;
begin
? CreateParams(Params);
? with Params do
? begin
??? FDefWndProc := WindowClass.lpfnWndProc;
??? ClassRegistered := GetClassInfo(WindowClass.hInstance, WinClassName, TempClass);
??? if not ClassRegistered or (TempClass.lpfnWndProc <> @MyInitWndProc) then
??? begin
????? if ClassRegistered then Windows.UnregisterClass(WinClassName,
??????? WindowClass.hInstance);
????? WindowClass.lpfnWndProc := @MyInitWndProc;
????? WindowClass.lpszClassName := WinClassName;
????? if Windows.RegisterClass(WindowClass) = 0 then RaiseLastOSError;
??? end;
??? MyCreationControl := Self;
??? CreateWindowHandle(Params);
??? if FHandle = 0 then RaiseLastOSError;
? end;
end;
procedure TMyWindow.DefaultHandler(var Message);
begin
? if FHandle <> 0 then
??? with TMessage(Message) do
????? Result := CallWindowProc(FDefWndProc, FHandle, Msg, WParam, LParam);
end;
function TMyWindow.GetHandle: HWnd;
begin
? HandleNeeded;
? Result := FHandle;
end;
procedure TMyWindow.HandleNeeded;
begin
? if FHandle = 0 then CreateHandle;
end;
procedure TMyWindow.MainWndProc(var Message: TMyMessage);
begin
? WndProc(Message);
end;
procedure TMyWindow.ShowWindow;
begin
? Windows.ShowWindow(FHandle, CmdShow);
end;
procedure TMyWindow.UpdateWindow;
begin
? Windows.UpdateWindow(FHandle);
end;
procedure TMyWindow.WndProc(var Message: TMyMessage);
begin
? if Message.Msg = WM_DESTROY then
??? PostQuitMessage(0)
? else
??? Dispatch(Message);
end;
end.
dpr文件建立一消息循環(huán),如下:
program Project1;
uses
? Windows,
? MyWindowUnit in 'MyWindowUnit.pas';
{$R *.res}
var
? MyWindow: TMyWindow;
? hWindow: HWND;
? msg: TMsg;
begin
? MyWindow := TMyWindow.Create;
? hWindow := MyWindow.Handle;
? MyWindow.ShowWindow;
? MyWindow.UpdateWindow;
? while GetMessage(msg, 0, 0, 0) do
? begin
??? TranslateMessage(msg);
??? DispatchMessage(msg);
? end;
? MyWindow.Free;
end.
?
來自:kk2000, 時(shí)間:2004-1-7 21:36:00, ID:2399408
? 老達(dá): 還有嗎?繼續(xù)。不過有些匯編還是看不懂!?
來自:savetime, 時(shí)間:2004-1-8 9:10:00, ID:2399768
xzgyb,你抄的函數(shù)都是比較關(guān)鍵的,不過 TControl.Parent 和 TWinControl.Showing 也是很關(guān)鍵的屬性,你跟蹤 TControl.SetParent 就知道我為什么這么說了。
昨晚夢(mèng)見李維在給我講為什么 TWndMethod 可以指向虛方法,早上一起來就給忘了。只好今天看一下匯編代碼了。
我感覺我快要理清 Delphi 的消息機(jī)制了,希望周五能做個(gè)總結(jié)。?
來自:xzgyb, 時(shí)間:2004-1-8 9:58:00, ID:2399892
老K,沒了,就這些,VCL一開始創(chuàng)建窗口的關(guān)鍵就是這些,
savetime:我的意思是VCL首次建窗口的時(shí)機(jī)就是發(fā)生在
GetTextHeight那段
?
來自:savetime, 時(shí)間:2004-1-8 10:09:00, ID:2399915
to xzgyb,
>> 我的意思是VCL首次建窗口的時(shí)機(jī)就是發(fā)生在GetTextHeight那段。
我閱讀 VCL 代碼后的理解是:
TWinControl的 繼承類盡可能地推遲 CreateWindowHandle 過程的執(zhí)行,為了更有效的使用資源。只有在一個(gè)控件確實(shí)需要 HWnd 時(shí)才真正創(chuàng)建窗口。GetTextHeight 是可能導(dǎo)致 CreateWindowHandle 被調(diào)用的函數(shù)之一。更常見的情況是,如下代碼:
var
? Panel: TPanel;
begin
? Panel := TPanel.Create(Self);
? Panel.Parent := Self;
end;
執(zhí)行完第一條語句,并不會(huì)真正創(chuàng)建窗口,因?yàn)?Panel 還沒有顯示,第二條語句執(zhí)行后,會(huì)觸發(fā)一系列 TControl 和 TWinControl 的事件,這時(shí) CreateWindowHandle 才會(huì)被調(diào)用。?
來自:xzgyb, 時(shí)間:2004-1-8 13:56:00, ID:2400416
SaveTime:
哦,明白了你的意思了。
我以前的意思就是找到一個(gè)主窗口的在讀取dfm后首先建立窗口的時(shí)機(jī)
其實(shí)歸根結(jié)底就是當(dāng)訪問一個(gè)窗口的Handle時(shí),調(diào)用HandleNeed,如果沒有創(chuàng)建則
創(chuàng)建,也就是一種惰性創(chuàng)建的機(jī)制,呵呵,惰性還是以前跟別人學(xué)的一個(gè)詞
祝你今晚還能夢(mèng)到李維,呵呵
老K:系統(tǒng)裝好了嗎?
來自:kk2000, 時(shí)間:2004-1-8 14:18:00, ID:2400478
老達(dá):要常來阿,讓我在這里多學(xué)習(xí)一點(diǎn)。真的又進(jìn)步了不少! 我
的系統(tǒng)昨天搞定了!就做個(gè)覆蓋安裝,好省事的。其它的東西并沒有掉。 ?
來自:savetime, 時(shí)間:2004-1-8 16:07:00, ID:2400797
惰性創(chuàng)建,有意思,在找不到更直接的稱呼之前可以先用這個(gè)詞 :)?
來自:book523, 時(shí)間:2004-1-8 16:41:00, ID:2400914
to savetime:
明天等你的總結(jié)啊。
看來李維今晚會(huì)夢(mèng)到你啊。?
來自:savetime, 時(shí)間:2004-1-8 17:04:00, ID:2400963
to book523,
我是希望像上周五一樣,一個(gè)晚上(通宵)上能夠?qū)懲昕偨Y(jié),只是這兩天搬貨和一堆雜事浪費(fèi)了很多時(shí)間。我盡力吧。?
來自:kk2000, 時(shí)間:2004-1-9 17:52:00, ID:2403112
TO: savetive 不是說今天來個(gè)總結(jié)嗎? 我每天都要來看看情況的!
等你的佳音。
?
來自:savetime, 時(shí)間:2004-1-9 17:58:00, ID:2403123
to kk2000,
我正在寫,我希望今晚能寫完,寫完后我會(huì)在這里發(fā)貼通知你。
我要下班了。?
來自:savetime, 時(shí)間:2004-1-10 0:06:00, ID:2403551
我的總結(jié)寫完了,歡迎批評(píng)指正。你如果有任何疑問,我都非常樂意盡力回答。
http://www.delphibbs.com/delphibbs/dispq.asp?lid=2403549?
來自:xzh2000, 時(shí)間:2004-1-10 20:32:00, ID:2404582
真是很勤奮,savetime.?
來自:wjh_wy, 時(shí)間:2004-1-11 13:46:00, ID:2405065
又是一篇好文章,學(xué)習(xí)。?
來自:savetime, 時(shí)間:2004-1-13 12:25:00, ID:2408561
多謝各位捧場(chǎng)。?
來自:henry103734338, 時(shí)間:2005-8-31 11:06:59, ID:3186394
good?
來自:yunxi126, 時(shí)間:2006-6-29 9:13:16, ID:3490901
so good!?
?
得分大富翁:baifeng-4,book523-20,chnplzh-4,dedema-4,einsteingod-4,ka52-4,kk2000-4,lance2000-4,renyi-4,tingliuxingyu-4,vc_delphi-4,Walnut_Tom-4,wjh_wy-4,WoDing-4,xchen.d-4,xff916-4,xzgyb-4,xzh2000-4,zeroyou-4,zhumoo-4,積步-4,
您尚未進(jìn)入本論壇 或者 您的賬號(hào)沒有經(jīng)過確認(rèn)。?
?
總結(jié)
以上是生活随笔為你收集整理的Delphi的对象机制浅探[转载]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【2015年第4期】基于大数据技术的P2
- 下一篇: 【软件测试】黑盒测试の正交试验法