----- 老鰓 -------- 考慮如下這個簡單類ttest
unit Unit1;interfaceuses
Windows, SysUtils, Variants, Classes;type
ttest = class
public
j:integer;
i:integer;
function aa(b,c: integer):integer;stdcall;
end;implementationfunction ttest.aa(b,c: integer):integer;stdcall;
begin
Result := b + i + c;
end;end.調用代碼如下var a:ttest;
j:integer;
begin
a := ttest.Create;
a.i := 50;
j:= a.aa(10,20);
end;一。觀察j := a.aa(10,20)的編譯結果:
[要點]:
按stdcall調用傳參數方式,從右至左將參數壓棧,因為是對象的函數調用,
所以最後將對象的地址壓棧,然後調用方法.
二。觀察aa成員函數的編譯結果:
[要點]:1.對象地址擷取:[ebp+$08],即最後一個壓棧的參數(stdcall,其他調用
方式根據壓棧順序可以同理計算出來)2.成員變數值的擷取方法,i的位移是8,因為是第二個整型數.三。根據上面的分析,可以用彙編實現aa成員函數如下:
{用彙編實現該函數如下}
function ttest.aa(b,c: integer):integer;stdcall;
asm
mov eax,[ebp+$0c] //Result := b
mov edx,[ebp+$08] //擷取對象/self地址 -> edx
add eax,[edx+i] //加上成員變數i的值(i在此為相對於self的位移:
//Result := Result + i;
add eax,[ebp+$10] //Result := Result + c;
end;
[要點]:
1.Delphi過程/函數內嵌彙編中只有eax/ecx/edx可以隨意使用,eax一般預設
作為函數的傳回值存放寄存器.
2.其它寄存器要在過程/函數內使用時,最好先壓棧,退出前還原.
----- 老鰓 -------- 本節將來詳細研究一下DELPHI的事件機制,事件在底層實踐上說白了就是過程/函數地址的擴充,一般過程/函數指標儲存的就是純粹的4個位元組(32位作業系統)的過程/函數地址,對比如下: type TSimpleEvent = procedure(Sender: TObject) of object; TProcPointer = procedure(Sender: TObject); 從定義上看,差別很明顯,在於事件多了個" of object ",什麼意思呢,因為事件程序往往定義在別的類的成員過程/函數,作為類成員過程/函數,肯定需要對象地址資訊(用來訪問對象成員變數),所以事件資訊中除了過程/函數地址外還需要一個對象地址,如此可以推測事件和一般過程/函數指標的大小應該不一樣,編寫代碼測試一個會發現: SizeOf(TSimpleEvent) 等於 8; SizeOf(TProcPointer ) 等於 4; 下面我們就來驗證一下以上的推測: 建立一簡單DELPHI工程,在form1中增加兩個TSimpleEvent事件:
unit Unit1;interfaceuses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;type
TSimpleEvent = procedure(Sender: TObject) of object;
TProcPointer = procedure(Sender: TObject); TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
Faa: TNotifyEvent;
Fcc: TNotifyEvent; procedure DoEvent(Sender: TObject);
procedure Setaa(const Value: TNotifyEvent);
procedure Setcc(const Value: TNotifyEvent);
public
property aa: TNotifyEvent read Faa write Setaa;
property cc: TNotifyEvent read Fcc write Setcc;
end; var
Form1: TForm1;
iEventAddr: Integer; //事件程序地址
i,j:integer;implementation {$R *.dfm}procedure TForm1.DoEvent(Sender: TObject);
begin
showmessage('DoEvent');
end; procedure TForm1.FormCreate(Sender: TObject);begin //設定事件屬性
aa := DoEvent;
cc := DoEvent; asm
mov eax,offset DoEvent
mov iEventAddr,eax
end; i := integer(@@cc); //cc事件變數地址!!事件變數前加一個@表示內容
j := integer(@@aa); //aa事件變數地址
end; procedure TForm1.Setaa(const Value: TNotifyEvent);
begin
Faa := Value;
end; procedure TForm1.Setcc(const Value: TNotifyEvent);
begin
Fcc := Value;
end; end. 在" j := integer(@@aa); //aa事件變數地址" 處設定斷點,運行到這裡後,再按F8跳到過程末尾,然後按Ctrl+Alt+C查看j地址處記憶體: 發現兩個事件變數儲存的前4個位元組都和iEventAddr(即事件處理過程DoEvent的地址)相同,即$4520C0,然後看看後4個位元組內容$D51FE0,是否就是當前表單對象地址呢,現在來驗證一下: 按CTRL+F7 查看@Form1 為$455BFC: 然後來看看該記憶體處的內容,果然為$D51FE0: 真相大白了,事件變數儲存的內容果然是過程/函數地址和過程/函數所屬類的對象地址,OK,先研究到這裡,欲知更詳細內幕,且聽下回繼續分解......