日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Delphi的对象机制浅探[转载]

發布時間:2025/3/15 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Delphi的对象机制浅探[转载] 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.


Delphi的對象機制淺探

savetime2k@yahoo.com
2004-1-3


前幾天開始閱讀 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????? ; dl <> 0 則調用 System._AfterConstruction
?????????????????????????????????????? ; (注意不是 TObject.AfterConstruction)
????????? pop dword ptr fs:[$00000000] ; fs:[0] 指向結構化異常處理的函數,此即取消最后一次的 try..except設置
?????????????????????????????????????? ; 這個 try..except 在 System._ClassCreate 中創建
?????????????????????????????????????? ; 用于在出錯時自動恢復堆棧/釋放內存分配/并調用 TObject.Free
????????? add esp, $0c???????????????? ; 恢復堆棧,注意只恢復了 12 字節的堆棧,還有4字節由上句 pop 了
????????? ret

注意:以上匯編代碼中重復出現了 test dl,dl,說明 Borland 并沒有特別對待 TObject.Create,TObject.Create確實是個空函數。TObject.Create的匯編代碼是由 constructor directive 指示編譯器形成的,編譯器對每個class 都一視同仁。
注意:這段 TObject.Create 代碼是在 PC 機上編譯的結果,嚴格地說應該是在 Win32 操作系統上的實現之一。查看System._ClassCreate 就知道 Borland 還有其他的異常處理實現機制,產生的 TObject.Create 代碼也不相同。

⊙ System._AfterContruction 函數的代碼:
function _AfterConstruction(Instance: TObject): TObject;
begin
? Instance.AfterConstruction;
? Result := Instance;
end;

⊙ System._ClassCreate 函數的代碼:
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 則不調用 TObject.NewInstance
??????? JL????? @@noAlloc
??????? CALL??? DWORD PTR [EAX] + VMTOFFSET TObject.NewInstance ; 調用 TObject.NewInstance
@@noAlloc:
{$IFNDEF PC_MAPPED_EXCEPTIONS}????????? ; 設置 PC 架構的結構化異常處理
??????? 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???????????????????? ; 恢復寄存器
??????? POP???? ECX
??????? POP???? EDX
??????? RET

{$IFNDEF PC_MAPPED_EXCEPTIONS}????????? ; 設置非 PC 架構的結構化異常處理
@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;


==============================================================
⊙ 測試目標:查看 constructor 函數中 inherited 的編譯器實現
==============================================================
⊙ 測試代碼及反匯編代碼:
type
? TMyClass = class(TObject)
??? constructor Create;
? end;
? constructor TMyClass.Create;
? begin
??? inherited; // 考查此句的實現
??? Beep;
? end;

procedure Test; register;
var
? Obj: TMyClass;
begin
? Obj := TMyClass.Create;
????????? mov dl, $01???????????????? ; class reference 時編譯器設置 dl = 1
????????? mov eax, [$004600ec]??????? ; 設置 eax 為指向 TMyClass 的 VMT pointer
????????? call TMyClass.Create??????? ; 調用 TMyClass.Create
????????? mov [ebp-$04], eax????????? ; 保存 新建對象的指針
end;

constructor TMyClass.Create 的反匯編代碼:
???????????????????????????????????????? ; 函數進入時 eax = pointer to VMT??????????? (dl = 1)
????????????????????????????????????????????????????? eax = pointer to instance?????? (dl = 0)
???????????????????????????????????????? ; 函數返回時 eax = pointer to instance
begin
????????? push ebp?????????????????????? ; 這3句用于保存堆棧指針和創建堆棧
????????? mov ebp, esp
????????? add esp, -$08?????????????????
????????? test dl, dl??????????????????? ; 如果 dl = 0 則跳到 @ClassCreate 之后 @@1 處執行
????????? jz +$08
????????? add esp, -$10????????????????? ; 為 _ClassCreate 調用準備堆棧
????????? call @ClassCreate????????????? ; 調用 System._ClassCreate,執行完成后 eax = 新建對象的指針
?????? @@1:
????????? mov [ebp-$05], dl????????????? ; 將 dl 值保存到堆棧中的 1 字節中,因為后面的 inherited TObject.Create
???????????????????????????????????????? ; 可能會改變 edx 的值
????????? mov [ebp-$04], eax???????????? ; 保存 eax 到堆棧, eax = pointer to instance
inherited;
????????? xor edx, edx?????????????????? ; 將 edx 清零(dl = 0),以通知 TObject.Create 不用再調用
???????????????????????????????????????? ;? _ClassCreate 和 AfterConstructor (編譯器實現)
????????? mov eax, [ebp-$04]???????????? ; 將 eax 的值還原為前面保存在堆棧的 eax 值
???????????????????????????????????????? ; (這句是多余的,但在其它情況下可能必須執行此句)
????????? call TObject.Create??????????? ; 調用 TObject.Create
Beep;
????????? call Beep????????????????????? ; 繼承類中 inherited 之后實現的功能
????????? mov eax, [ebp-$04]???????????? ; 將 eax 的值還原為前面保存在堆棧的 eax 值
????????? cmp byte ptr [ebp-$05], $00??? ; (間接)檢查 dl 是否 = 0
????????? jz +$0f??????????????????????? ; dl = 0 則跳過 _AfterConstruction 到 @@2 處
????????? call @AfterConstruction??????? ; 調用 System._AfterConstruction
????????? pop dword ptr fs:[$00000000]?? ; 這2句恢復為 _ClassCreate 創建的堆棧空間
????????? add esp, $0c
?????? @@2:
????????? mov eax, [ebp-$04]???????????? ; 返回 pointer to instance
end;
????????? pop ecx
????????? pop ecx
????????? pop ebp
????????? ret

結論:真是精妙!一個對象的正常的創建(Obj := TMyObj.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。

? 看上去有點繁雜,可是如果讀懂了上面 TObject.Create 和 TMyObject.Create 則會感覺對象的創建非常清晰。

?

==================================================================================
⊙ 測試目標:以 object reference 和 class reference 調用構造函數的編譯器實現
==================================================================================
⊙ static constructor 測試代碼及反匯編代碼 (省略了begin 和 end 后面的堆棧分配代碼):
procedure Test; register;
var
? Obj: TObject;????????
begin
? Obj := TObject.Create;
????????? mov dl, $01?????????????? ; 采用 class reference 時編譯器自動設置 dl = 1
????????? mov eax, [$004010a0]????? ; 把指向 TObject class VMT 的指針存入 eax,用于下一行調用
????????? call TObject.Create
????????? mov [ebp-$04], eax
? Obj := Obj.Create;
????????? or edx, -$01????????????? ; 采用 object reference 時編譯器自動設置 edx 的所有 bit 都為 1
????????? mov eax, [ebp-$04]??????? ; 把 Obj 指針的所指的區域(即對象內存空間)存入 eax,用于下一行調用
????????? call TObject.Create??????
????????? mov [ebp-$04], eax
end;

⊙ virtual constructor測試代碼及反匯編代碼 (省略了begin 和 end 后面的堆棧分配代碼):
procedure Test; register;
var
? Comp: TComponent;
begin
? Comp := TComponent.Create(nil);
????????? xor ecx, ecx??????????????????? ; 設置 參數 = nil
????????? mov dl, $01???????????????????? ; 設置 dl = 1
????????? mov eax, [$00412eac]??????????? ; 設置 eax = class VMT pointer
????????? call TComponent.Create????????? ; 調用 TComponent.Create
????????? mov [ebp-$04], eax????????????? ; 保存 新建的對象至 Comp
? Comp := Comp.Create(nil);
????????? xor ecx, ecx??????????????????? ; 同上
????????? or edx, -$01??????????????????? ; 設置 edx 所有位為 1
????????? mov eax, [ebp-$04]????????????? ; 這句和下句 設置 ebx 為 TComponent class 的 VMT pointer
????????? mov ebx, [eax]????????????????? ; (如果 Comp 已經實例化了,則 ebx 的值是對的)
????????? call dword ptr [ebx+$2c]??????? ; 可能是調用 TComponent.Create(Comp, -1, nil);
????????? mov [ebp-$04], eax????????????? ; 保存 新建的對象至 Comp
end;

結論:object reference 方式的 constructor 調用,編譯器嘗試實現為 inherited 調用,結果當然是錯誤。


=======================================================================
⊙ 測試目標:考查 Object 和 Class 在調用 class method 時的編譯器實現
=======================================================================
⊙ 測試代碼及反匯編代碼 (省略了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 類型的參數傳遞的
????????? mov eax, [$00412eac]????????????? ; eax = pointer to class VMT
????????? call TObject.ClassName
end;

結論:class method 的調用隱含參數 eax 為指向 VMT 的指針,不管是用 class 還是 object 方式調用,編譯器都會正確地把指向 class VMT 的指針傳遞給 eax。


========================================================================
⊙ 測試目標:考查 ShortString 返回值類型的函數沒有賦值時編譯器的實現
========================================================================
procedure Test; register;
begin
? TComponent.ClassName;
????????? lea edx, [ebp-$00000100]????? ; 編譯器會在堆棧中創建256 byte 的臨時空間,以保證 edx 不會為非法值
????????? mov eax, [$00412eac]?????????
????????? call TObject.ClassName
end;

⊙ TObject.ClassName 函數代碼:
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]??????????????? ; 設置 result string 的 length
??????? INC???? ECX
??????? REP???? MOVSB
??????? POP???? EDI
??????? POP???? ESI
end;
{$ENDIF}

結論:這只是我想了解字符串返回值的傳遞方式。

===================
?????? (完)
===================
?


來自:lance2000, 時間:2004-1-3 9:46:00, ID:2390276
寫的非常好!?


來自:dedema, 時間:2004-1-3 9:57:00, ID:2390302
這么厲害啊!一個晚上就完成了!?


來自:積步, 時間:2004-1-3 10:10:00, ID:2390324
mark?


來自:zhumoo, 時間:2004-1-3 10:26:00, ID:2390353
高手就是高手,學習.?


來自:kk2000, 時間:2004-1-3 15:05:00, ID:2390894
  樓主一個晚上完成,只能說佩服了!?


來自:renyi, 時間:2004-1-3 17:54:00, ID:2391175
厲害,不知 Delphi 的對象機制和 Java 、C# 相比,哪個個的效率更高??


來自:einsteingod, 時間:2004-1-3 19:07:00, ID:2391225
寫的非常好!?


來自:xff916, 時間:2004-1-3 19:49:00, ID:2391246
牛呀,學習 ,學習,在學習?


來自:積步, 時間: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 立即數就可以顯示為 100, 如果不改就顯示為其它值。
??? MOV tmpInt, ECX;
? end;
? ShowMessage(IntToStr(tmpInt));
? A.Free;
end;?


來自:savetime, 時間:2004-1-4 1:30:00, ID:2391511
to 積步:

DELPHI 對“MOV ECX, A.FA”生成的代碼實際上是以 Record 的類型生成的,這樣 ECX 的值就是
變量 A 的地址 加上 TA.FA 的偏移值,結果是 ECX 變成堆棧上的一個值,所以不對。

A.FA 的實際地址是 A 指向的地址(也就是對象內存位置,而不是 A 的地址)加上 FA 相對于對象
頭部的偏移地址。我湊出以下的代碼,可以實現你要的結果:
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 會不會自動保護其他使用 EBX 的語句。如果你知道在混合
匯編的情況下如何使用寄存器請教我一下。

其實我真的不知道還有這種方法可以獲得私有成員,有趣!?


來自:savetime, 時間:2004-1-4 2:40:00, ID:2391532
內容更正:

我發現自己在上文注釋中的一個錯誤,在以下匯編第三行的“ push ecx ”我把它注釋為
? “保存 ecx (無用的語句)”,
更正為:
? “分配局部變量 Obj 的堆棧空間”。

原來 add esp, -$4? 花費 3 個字節的指令
而?? push ecx????? 只要 1 個字節的指令,執行更快

大家現在知道我不是高手了吧,我是一邊翻匯編手冊,一邊寫注釋的。我只能毫不謙虛地
說:我真的是初學者。高手看這些簡單的匯編代碼哪里需要花一個晚上。

希望大家關注內容,不要只是說“好”,重要的是“有沒有錯誤”,這樣才能提高。


出錯的注釋段:
=================================================
⊙ 測試目標:查看 TObject.Create 的編譯器實現
=================================================
⊙ 測試代碼及反匯編代碼:
procedure Test; register;
var
?Obj: TObject;????????
begin
???????? push ebp???????????????????? ; 前2句用于設置堆棧指針
???????? mov ebp, esp
???????? push ecx???????????????????? ; 保存 ecx (無用的語句) -> (更正為) 分配局部變量 Obj 的堆棧空間
?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

?


來自:savetime, 時間:2004-1-4 2:40:00, ID:2391533
to 積步,
我測試了在混合匯編的情況下修改寄存器時的實現,結果是:Delphi 會自動把嵌入匯編中修改了的寄存器備份在堆
棧中,所以可以隨意使用 Delphi 允許的寄存器。?


來自:book523, 時間:2004-1-4 10:20:00, ID:2391614
結論:真是精妙!一個對象的正常的創建(Obj := TMyObj.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。

?看上去有點繁雜,可是如果讀懂了上面 TObject.Create 和 TMyObject.Create 則會感覺對象的創建非常清晰。
-------------------------------------------------------------------------------
為什么要保存經常調用dl的值?
Dl主要用來表示是class級別調用還是對象級別調用,
class級別調用時,constructor會自動執行
System._ClassCreate,NewInstance,InitInstance,AfterConstruction等過程,
然后才是Constructor中的代碼,
而對象級別調用時,只會執行Constructor中的代碼。?


來自:savetime, 時間:2004-1-4 12:18:00, ID:2391785
to book523,

你說的是我在“⊙測試目標:以 object reference 和 class reference 調用構造函數的編譯器實現”中的結果吧。我在文章中寫的結論是:“object reference 方式的 constructor 調用,編譯器嘗試實現為 inherited 調用”

其實如果你看了測試代碼的反匯編過程,就應該知道我的這個結論是錯誤的。Borland 在 Object Pascal Reference 中寫的就是你說的“對象級別調用時,只會執行Constructor中的代碼。”,可是事實上不是這樣。

在形式如下的代碼中
? AComp := AComp.Create
Borland 先將 edx 所有 bit 設置為 1 ,也就是 dl 為 1,也就是仍然嘗試沿用 class 級別的調用。可是你看 Delphi 的生成的匯編代碼不知所云,所以根本就沒有所謂的“對象級別調用”。我認為 Borland 應該對 AComp := AComp.Create 調用提示為語法錯誤。我實在是想不到什么時候會需要這種形式的調用。

我測試這樣的調用方式是因為我經常在創建 Form 時忘了寫 T 這一標識符:
? ChildForm := ChildForm.Create(nil); // 這里應該是 TChildForm.Create(nil)
我想知道這樣的結果是什么。
?


來自:vc_delphi, 時間:2004-1-4 18:27:00, ID:2392395
高手就是高手,學習.?


來自:積步, 時間:2004-1-4 21:30:00, ID:2392563
to? savetime:
多謝指教。
以前也研究過匯編什麼東東的, 但是現在沒有多少時間研究。?


來自:積步, 時間: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 都是得到當前對象的第一變量
??? //MOV EAX, [EAX] + 8 訪問第二個變量, 依此類推??
??? //MOV EAX, [EAX], 是指向VMT的指針
?? MOV tmpInt, EAX;
? end;
? ShowMessage(IntToStr(tmpInt));
? A.Free;
end;

這樣也可以實現, 但是想不到savetime兄弟還有更高的招術, 厲害厲害!!?


來自:xchen.d, 時間:2004-1-4 21:33:00, ID:2392650
我看‘中國有救了‘,真有耐力!呵呵
心不靜,做不了?


來自:savetime, 時間:2004-1-5 10:15:00, ID:2392767
to 積步,
? 我又從你這里學到一點匯編知識,知識就是這樣積累出來的呀。

to everybody,
其實要看懂文中的匯編并不難,我做的這件工作只是 Delphi 的基礎而已。讀懂和會用根本上是兩回事。
我現在正在分析 TWinControl 如何如何封裝 Windows 的消息系統,二天過去,進展不大。相關的函數太多了,還有一些匯編夾在其中。我認為消息系統才是 VCL 的關鍵地方。整個程序的執行過程全在里面。?


來自:tingliuxingyu, 時間:2004-1-5 10:56:00, ID:2393353
高手啊,高手,想法啊,想法,學習啊,學習?


來自:book523, 時間:2004-1-5 11:35:00, ID:2393464
我認為 Borland 應該對 AComp := AComp.Create 調用提示為語法錯誤。我實在是想不到什么時候會需要這種形式的調用。
--------------------------------------------------------------------------------
看看TApplication的CreateForm過程,有一個典型的
AComp := AComp.Create 例子,有些時候生成的匯編是不知所云,
那是因為我們對匯編根本就不了解,匯編的語句單個拿出來都很好懂,
但是要一段拿出來要明白要實現什么功能就困難了。

procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
var
? Instance: TComponent;
begin
? Instance := TComponent(InstanceClass.NewInstance); //調用NewInstance方法分配內存,創建框架等等。然后根據TComponent類圈定框架賦給Instance
? TComponent(Reference) := Instance;//給Reference變量賦值
? try
??? Instance.Create(Self);//執行constructor Create(Owner) 的代碼
? except
??? TComponent(Reference) := nil;
??? raise;
? end;
? if (FMainForm = nil) and (Instance is TForm) then
? begin
??? TForm(Instance).HandleNeeded; //調用HandleNeeded過程,創建窗體。
??? FMainForm := TForm(Instance);//把第一個創建的窗體作為主窗體。
? end;
end;?


來自:savetime, 時間:2004-1-5 11:53:00, ID:2393534
to book523,
我又錯了,你真的找出了這樣的代碼,厲害!我要考慮一下再回復你。?


來自:book523, 時間:2004-1-5 12:41:00, ID:2393636
?? 我現在正在分析 TWinControl 如何如何封裝 Windows 的消息系統,二天過去,進展不大。相關的函數太多了,還有一些匯編夾在其中。我認為消息系統才是 VCL 的關鍵地方。整個程序的執行過程全在里面。
---------------------------------------
?? 我現在也在做這個工作,昨天看了李維的那本新書《inside vcl》開始有點頭緒了,
你不要從TWinControl開始,那樣會不知所云,你可以從項目工程文件的那三句話開始
分析,估計很快就能理出個大概來。
? Application.Initialize;
? Application.CreateForm(TForm1, Form1);
? Application.Run;
?


來自:book523, 時間:2004-1-5 12:45:00, ID:2393645
我又錯了,你真的找出了這樣的代碼,厲害!我要考慮一下再回復你。
---------------------------------------------------------------
正好昨天分析這個過程看到的。?


來自:zeroyou, 時間:2004-1-5 13:41:00, ID:2393797
到此一游,?


來自:savetime, 時間:2004-1-5 13:50:00, ID:2393802
to book523,

我找到錯誤的的原因了。我把匯編代碼 jz 和 jl 搞混了,所以上面注釋 System._ClassCreate 有錯誤。

其實 dl 寄存器有 3 種狀態(我原來以為是 2 種):

dl = 1? 是在 TClass.Create 之前被設置
dl = 0? 是在 Inherited???? 之前被設置
dl = -1 是在 Object.Create 之前被設置

dl = 1? System._ClassCreate 被調用
dl = 0? System._ClassCreate 不被調用
dl = -1 System._ClassCreate 被調用, 但不執行 NewInstance 工作,只是設置異常斷點


? “⊙測試目標:以 object reference 和 class reference 調用構造函數的編譯器實現” —— 的注釋也有錯誤。

重讀 Obj.Create 的匯編代碼,終于明白了應該是有 Object Reference consturction 這一事實。只是在 Obj.Create 之前必須用 NewInstance 分配內存和設置 Obj VMT 的指針,否則 Obj.Create 就會失敗。Obj.Create 不會調用 NewInstance,但會設置異常處理,保證出錯時析構函數被調用。

====================================

對于 VCL 的消息系統,我原本也想從上向下讀,不過我對 TWinControl 沒什么了解,只好先看它都包裝了哪些函數。我現在可以用你推薦的方法試試。
我猜想 TApplication 只是調用 GetMessage 再 DispatchMessage,所以關鍵的是 VCL 如何注冊 WndProc,并把注冊的這個 WndProc 關聯到對象上。

MakeObjectInstance 很有意思,在內存中建立一塊一塊的 ObjectInstance 代碼,ObjectInstance 的地址又被注冊為標準的 Windows Procedure。

我是從 TWinControl.Create 入手的,第一句不太明白,
? FObjectInstance := Classes.MakeObjectInstance(MainWndProc);
? { function MakeObjectInstance(Method: TWndMethod): Pointer }

你能解釋一下 MakeObjectInstance 的 MainWndProc 傳遞的實際內容是什么(是不是傳遞 MainWndProc 的指針和 Self 指針?我是不太明白為什么編譯器知道要把 Self 傳過去。)

====================================

你已經拿到 Inside VCL 了,幸福啊。我們這里的書店太差了,到今天還沒通知我到貨。?


來自:baifeng, 時間:2004-1-5 13:52:00, ID:2393836
g z?


來自:book523, 時間:2004-1-5 14:26:00, ID:2393903
你能解釋一下 MakeObjectInstance 的 MainWndProc 傳遞的實際內容是什么(是不是傳遞 MainWndProc 的指針
和 Self 指針?我是不太明白為什么編譯器知道要把 Self 傳過去。)
============================================================================
傳遞的是MainWndProc 的指針和 Self 指針,看delphi的消息結構:
? 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中的消息結構:
typedef struct tagMSG
{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
顯然少了一個最重要的field即窗口句柄hwnd。
因此在delphi中處理消息的方法都會把self作為隱含參數,
把self壓入到EAX中,再把TMessage 結構的指針作為第二個參數,放入EDX中,
這樣才符合windows的回調函數的格式,
實際上MainWndProc 正是delphi窗體類的回調函數。

?


來自:book523, 時間:2004-1-5 14:32:00, ID:2393915
?你上面對Create過程的跟蹤分析真是精辟啊,令人佩服啊。

========================================================

你已經拿到 Inside VCL 了,幸福啊。我們這里的書店太差了,到今天還沒通知我到貨。

===============================
我是在dearbook上訂了,12。31號訂的,2。2號就拿到了。
?


來自:book523, 時間:2004-1-5 14:39:00, ID:2393940
to savetime:
看到了帖子嗎:
http://www.delphibbs.com/delphibbs/dispq.asp?lid=584889
我從收藏夾中提上來的。?


來自:savetime, 時間:2004-1-5 16:24:00, ID:2394288
看到,正在研究。?


來自:WoDing, 時間:2004-1-5 21:24:00, ID:2394955
留下記號,到此一游?


來自:kk2000, 時間:2004-1-6 9:45:00, ID:2395470
? TO:savetime 、積步 我也正在學習匯編。其實我也有savetime的想法。可是自己能力
和時間的問題。只能向你們學習了。關于:積步兄的問題確實有趣!而且 savetime兄的回答就精妙了! 現在我調試一下結果是下面的:
1.這是(savetime)做的:MOV ECX, TA(EBX).FA;??? // 通知編譯器 EBX 指向的是 TA class
其實這句話可以這樣翻譯過來(這是我的個人看法,有步不的地方請指正):
asm
??? MOV EAX, [A];//這句是把這個實例的引用傳進寄存器EAX,也就是堆中的首地址
??? //MOV EAX,TA(EAX).FA;
??? MOV tmpInt,EAX ;//這里是把寄存器中的值(也就是實例的首地址,而不是變量的地址)傳給tempInt;
? end;
//在這里也就是相當于(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 立即數就可以顯示為 100, 如果不改就顯示為其它值。(這里修改一下)
?? MOV tmpInt, ECX;
?end;
?ShowMessage(IntToStr(TA(tmpInt).FA));//主意這里如果改為這樣就對了,這里可能是編譯器做的
?A.Free;
end;
其實(積步)的做法就是得到這個類的段地址的FA的偏移地址。而不是該地址
里面的內容,至于怎么樣取出我們要找到該變量地址的值,就象(savetime)那樣
做!但是那樣那做為什么能夠取出他的值?后來(積步)用寄存器加上立即數也可以
取出來,我還是模糊???

?

?


來自:savetime, 時間:2004-1-6 10:19:00, ID:2395616
to kk2000:
你的問題很簡單
?? MOV EAX, A;? // A 是指向對象的指針,這句把對象在內存中的地址存入 EAX
?????????????????? 這時 EAX + 4 就是對象的第一個成員變量
?????????????????? TA(EAX).FA 就是 EAX 加上 FA 的偏移處的內容
?????????????????? 這是 Delphi 語法支持的,能大概看得懂就行了。

你可是解決了一個大問題,不用匯編也能訪問私有成員,我原以為是匯編的特權呢。
procedure TForm1.Button1Click(Sender: TObject);
var
? A: TA;
begin
? A := TA.Create;
? A.SetA(100);
? ShowMessage(IntToStr(TA(A).FA));
? A.Free;
end;
?


來自:savetime, 時間:2004-1-6 10:28:00, ID:2395657
我錯了,我忘記同一個單元的類可以互相訪問私有成員,真是該死。?


來自:chnplzh, 時間:2004-1-6 10:40:00, ID:2395707
收藏!?


來自:book523, 時間:2004-1-6 11:47:00, ID:2395949
to savetime:
?? 你做什么工作啊,好像挺有時間的啊。
有時間到www.01cn.net上看看,是個好地方啊。?


來自:savetime, 時間:2004-1-6 12:05:00, ID:2396007
to book523,
www.01cn.net 真是高手云集啊,收藏!
我在公司做雜務,主要是收發貨、檢查客戶退回來的產品。我總是盡快把雜務處理掉,其他的時間就自己寫點小程序,讀讀書。我也想去軟件公司,可是沒有人要我,學歷太低,專業知識也不夠。只好先混著。?


來自:ka52, 時間:2004-1-6 12:43:00, ID:2396097
看了看.感覺這才是真正的高手之路 .
可偶對匯編太沒感覺了..我開始看看vcl吧 :)?


來自:Walnut_Tom, 時間:2004-1-6 13:52:00, ID:2396282
這里也是高手如云呀?


來自:book523, 時間:2004-1-6 14:03:00, ID:2396310
01cn上很多都是在這里已成名的高手。
當然現在人氣還不夠。?


來自:savetime, 時間:2004-1-6 16:08:00, ID:2396594
昨天只看完了 Windows Callback FObjectInstance 方法的匯編代碼。如果 Borland 把 TMessage 設計為 Result 字段在最前面,就不用把 Windows 回調前在堆棧中建立的參數再 PUSH 一遍了,直接把 Windows 傳過來的 HWND 設置為 0 后當作 Result 用就行了(因為MainWndProc 并沒有使用到 HWND)。這樣的話 StdWndProc 就可以設計為只有一段小段代碼而不是函數,處理消息的效率可以稍微提高一點。看來 Borland 寧愿降低效率也不愿放棄語法的美感。

下面是從 Windows Callback 開始到 TWinControl.MainWndProc 被調用的匯編代碼:

DispatchMessage(&Msg)??? // Windows 準備回調

Windows 準備回調 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?? ;調用 InstanceBlock.Code

InstanceBlock.Code:
??????????? pop ecx?????????????? ;將 eip.Next 的值存入 ecx, 用于取 @MainWndProc 和 Self
??????????? jmp StdWndProc??????? ;跳轉至 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


對于 VCL 消息系統的學習,這只是個開始。在 TWinControl.CreateWnd 方法中設置斷點,查看什么時候建立窗口,結果看到的調用堆棧是下面這樣:
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

真是太復雜了,除了硬著頭皮一行行代碼讀下去,還有什么好辦法呢?
?


來自:savetime, 時間:2004-1-6 17:55:00, ID:2396907
瀏覽了一個 Application 的執行過程,把過程簡單描述一下:

begin
首先調用了一個隱含的過程 _InitExe
_InitExe 初始化了 Module 信息,然后調用 _StartExe
_StartExe 設置異常等,然后調用 InitUnits
InitUnits 調用各個 Units 的 Initialization 段
其中調用到了 Controls.pas 的 Initializaiotn 段
Controls.Initialization 調用 InitControls
InitControls 主要建立 Mouse, Screen和 Application 實例
Application.Create 調用 Application.CreateHandle
Application.CreateHandle 建立一個窗口,并設置 Application.WndProc 為回調函數
Application.WndProc 主要處理一些應用程序級別的消息

然后才是 Project 的第一句: Application.Initialize;
這個過程基本上沒有內容,主要是讓用戶設置一個初始化函數

然后是 Project 的第二句: Application.CreateForm(TForm1, Form1);
新增 Form1的內存實例
調用 Form1.Create? -> TCustomForm.Create(Self)
TCustomForm.Create 調用 InitInheritedComponent
InitInheritedComponent 調用 InternalReadComponentRes
InternalReadComponentRes 調用 TStream.ReadComponent
TReader.ReadRootComponent 調用 TCustomForm.ReadState
TCustomForm.ReadState 調用到了 GetTextHeight
TCustomForm.GetTextHeight 調用 TCanvas.TextHeight
TCanvas.TextExtent 調用 TCanvas.RequiredState
這時候才標識出 TControlCanvas.CreateHandle
TControlCanvas.CreateHandle 又調用了 TWinControl.GetDeviceContext
TWinControl.GetDeviceContext 要求使用 Handle 于是調用 TWinControl.GetHandle
TWinControl.GetHandle 需要HWnd,于是調用TWinControl.HandleNeeded
由于沒有建立 HWnd,于是調用 TWinControl.CreateHandle
TWinControl.CreateHandle 這才調用 TCustomForm.CreateWnd 建立窗口
TCustomForm.CreateWnd 調用 TScrollingWinControl.CreateWnd
TScrollingWinControl.CreateWnd 調用 TWinControl.CreateWnd這時才真正建立了一個窗口
真是漫漫長路。


最后是:Application.Run;
我還沒看代碼,估計是建立消息循環之類。


看來不能這樣跟蹤,相關流操作太多,今天回家單獨建立一個 TForm 跟蹤一下。
下班了!?


來自:xzgyb, 時間:2004-1-7 21:02:00, ID:2399360
最近在看<<windows程序設計>>,對windows編程稍懂了點,下午照vcl抄了一下,只是主要的窗口建立部分,不知對你有沒有用,如下

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文件建立一消息循環,如下:
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, 時間:2004-1-7 21:36:00, ID:2399408
? 老達: 還有嗎?繼續。不過有些匯編還是看不懂!?


來自:savetime, 時間:2004-1-8 9:10:00, ID:2399768
xzgyb,你抄的函數都是比較關鍵的,不過 TControl.Parent 和 TWinControl.Showing 也是很關鍵的屬性,你跟蹤 TControl.SetParent 就知道我為什么這么說了。
昨晚夢見李維在給我講為什么 TWndMethod 可以指向虛方法,早上一起來就給忘了。只好今天看一下匯編代碼了。
我感覺我快要理清 Delphi 的消息機制了,希望周五能做個總結。?


來自:xzgyb, 時間:2004-1-8 9:58:00, ID:2399892
老K,沒了,就這些,VCL一開始創建窗口的關鍵就是這些,
savetime:我的意思是VCL首次建窗口的時機就是發生在
GetTextHeight那段
?


來自:savetime, 時間:2004-1-8 10:09:00, ID:2399915
to xzgyb,
>> 我的意思是VCL首次建窗口的時機就是發生在GetTextHeight那段。

我閱讀 VCL 代碼后的理解是:
TWinControl的 繼承類盡可能地推遲 CreateWindowHandle 過程的執行,為了更有效的使用資源。只有在一個控件確實需要 HWnd 時才真正創建窗口。GetTextHeight 是可能導致 CreateWindowHandle 被調用的函數之一。更常見的情況是,如下代碼:
var
? Panel: TPanel;
begin
? Panel := TPanel.Create(Self);
? Panel.Parent := Self;
end;
執行完第一條語句,并不會真正創建窗口,因為 Panel 還沒有顯示,第二條語句執行后,會觸發一系列 TControl 和 TWinControl 的事件,這時 CreateWindowHandle 才會被調用。?


來自:xzgyb, 時間:2004-1-8 13:56:00, ID:2400416
SaveTime:
哦,明白了你的意思了。
我以前的意思就是找到一個主窗口的在讀取dfm后首先建立窗口的時機
其實歸根結底就是當訪問一個窗口的Handle時,調用HandleNeed,如果沒有創建則
創建,也就是一種惰性創建的機制,呵呵,惰性還是以前跟別人學的一個詞
祝你今晚還能夢到李維,呵呵

老K:系統裝好了嗎?


來自:kk2000, 時間:2004-1-8 14:18:00, ID:2400478
  老達:要常來阿,讓我在這里多學習一點。真的又進步了不少! 我
的系統昨天搞定了!就做個覆蓋安裝,好省事的。其它的東西并沒有掉。 ?


來自:savetime, 時間:2004-1-8 16:07:00, ID:2400797
惰性創建,有意思,在找不到更直接的稱呼之前可以先用這個詞 :)?


來自:book523, 時間:2004-1-8 16:41:00, ID:2400914
to savetime:
明天等你的總結啊。
看來李維今晚會夢到你啊。?


來自:savetime, 時間:2004-1-8 17:04:00, ID:2400963
to book523,
我是希望像上周五一樣,一個晚上(通宵)上能夠寫完總結,只是這兩天搬貨和一堆雜事浪費了很多時間。我盡力吧。?


來自:kk2000, 時間:2004-1-9 17:52:00, ID:2403112
 TO: savetive  不是說今天來個總結嗎? 我每天都要來看看情況的!
等你的佳音。 
?


來自:savetime, 時間:2004-1-9 17:58:00, ID:2403123
to kk2000,
我正在寫,我希望今晚能寫完,寫完后我會在這里發貼通知你。
我要下班了。?


來自:savetime, 時間:2004-1-10 0:06:00, ID:2403551
我的總結寫完了,歡迎批評指正。你如果有任何疑問,我都非常樂意盡力回答。
http://www.delphibbs.com/delphibbs/dispq.asp?lid=2403549?


來自:xzh2000, 時間:2004-1-10 20:32:00, ID:2404582
真是很勤奮,savetime.?


來自:wjh_wy, 時間:2004-1-11 13:46:00, ID:2405065
又是一篇好文章,學習。?


來自:savetime, 時間:2004-1-13 12:25:00, ID:2408561
多謝各位捧場。?


來自:henry103734338, 時間:2005-8-31 11:06:59, ID:3186394
good?


來自:yunxi126, 時間: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,
您尚未進入本論壇 或者 您的賬號沒有經過確認。?
?

總結

以上是生活随笔為你收集整理的Delphi的对象机制浅探[转载]的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。