delphi程序设计之底层原理
雖然用delphi也有7,8年了,但大部分時間還是用在系統的架構上,對delphi底層還是一知半解,今天在網上看到一篇文章寫得很好,雖然是07年的,但仍有借鑒的價值。
現摘錄如下:
Delphi程序設計之--經驗技巧
這些日子太忙了,今天把剩下的部分貼完,希望對大家有用。看過前一篇的都知道此文的作者和出處,我就不詳細說了。
?
{ No. 16 }
//對于記錄類型Record的分析。
實例:
type
?TBaseRec = record
?? rStr: Integer;
?? rStr2: String;
?? rStr3: String;
?end;
?TStrRec = record
?? rStr: Integer;
?? rStr2: String;
?? rStr3: String;
?? rStr4: String;
?end;
procedure TForm1.Button3Click(Sender: TObject);
var
?vRec1: TStrRec;
?vBaseRec: TBaseRec;
begin
?vRec1.rStr := 1;
?vRec1.rStr2 := '123123';
?vRec1.rStr3 := '1';
?vRec1.rStr4 := '1';
?vBaseRec := TBaseRec(Pointer(@vRec1)^);
?ShowMessage(IntToStr(vBaseRec.rStr) + '_' + vBaseRec.rStr2 + '_' + vBaseRec.rStr3);
//
end;
{說明:
1、記錄類型互相轉換時,必須保證基礎Record類型,數據大小Sizeof應小于或等于擴展類型。保證轉換后的記錄類型對象的數據訪問合法正確。
2、在Delphi中,使用記錄類型互相轉換最為平凡的就是在消息Record的實現上了。在Delphi中定義了若干于TMessage可同時描述消息接受信息的Record,如:
?TWMKey = packed record
?? Msg: Cardinal;
?? CharCode: Word;
?? Unused: Word;
?? KeyData: Longint;
?? Result: Longint;
?end;
?當需要接受KeyDown和KeyUp的消息時,我們即可以使用TMessage也可以使用TWMKey作為消息接收的參數類型。因為Delphi為我們提供了若干便利的消息類型,所以我們在使用消息處理問世時就不會象VC中那樣繁瑣和易錯了。
3、記錄類型的使用還提供了一個不同語言間數據信息封裝訪問的途徑。在不同語言間使用記錄類型和記錄類型指針時,應注意內部定義的變量的類型匹配問題。
記錄類型的本質測試研究:
更改上面例子的實現部分,測試:
procedure TForm1.Button3Click(Sender: TObject);
var
?vRec1: TStrRec;
?rStr: Integer;
?rStr2: String;
?rStr3: String;
?vpt: Integer;
begin
?vRec1.rStr := 1;
?vRec1.rStr2 := '123123';
?vRec1.rStr3 := '1';
?vRec1.rStr4 := '1';
?vpt := Integer(@vRec1);
?rStr := Integer(Pointer(vpt)^);
?vPt := vPt + Sizeof(rStr);
?rStr2 := String(Pointer(vpt)^);
?vPt := vPt + Sizeof(rStr2);
?rStr3 := String(Pointer(vpt)^);
?ShowMessage(IntToStr(rStr) + '_' + rStr2 + '_' + rStr3);
end;
提示信息于開始例子相同,則推測:
1、Record類型中定義的數據是在一個連續空間中保存的
2、當定義函數時,如果考慮到函數處理的信息可能在后續版本中,需要擴充則可以使用記錄變量的方式傳遞參數。當擴充函數時只需將記錄變量根據此記錄的版本號轉換為對應的記錄類型變量進行訪問即可。具體實例可以參考Windows API函數的版本升級及擴展情況。
}
2003-6-17 19:52:00???
?2003-6-17 22:22:02??? 事件類型屬性,通過屬性賦值函數操作
{ No. 17 }
{在TypInfo單元中,有若干函數可以讓我們操作Dephi管理的類的VMT。通過,屬性名稱和對象VTM直接訪問或改變屬性值。
公共的屬性訪問函數:GetPropValue;公共的屬性設置函數:SetPropValue。其中,對事件屬性信息的讀取可以使用GetPropValue,但是卻不能通過SetPropValue給事件屬性賦值。
解決方案:使用SetMethodProp給控件屬性賦值。procedure SetMethodProp(Instance: TObject; const PropName: string; const Value: TMethod); overload;其中,TMethod 用以描述操作函數。
?TMethod = record
?? Code,? //函數地址;可以通過類函數MethodAddress,取得函數地址。其中,只有聲明在Published段的函數才能通過MethodAddress訪問。
?? Data: Pointer; //對象地址
?end;
}
//**************** 正常使用
type
?TMyForm = class(TForm)
? ...
?private
?? FMyText: String;
?published
?? procedure MyClick(Sender: TObject);
?end;
?
//窗體中,按鈕事件;實現動態分配另一個按鈕的事件的方法
procedure TMyForm.Button1OnClick(Sender: TObject);
var
?vMethod: TMethod;
begin
?FMyText := 'Hello Joy!';
?vMethod.Code := Self.MethodAddress('MyClick');????? //************ Code 1
?vMethod.Data := Self;?????????????????????????????? //************ Code 2
?SetMethodProp(Button2, 'OnClick', vMethod);
end;
procedure TMyForm.MyClick(Sender: TObject);
begin
? ShowMessage('Ok!');??????????????????????????????? //************ Show 1????
? ShowMessage(Self.FMyText);??????????? //************ Show 2?????
end;
?
//**************** 修改 一
//將[Code 1]和[Code 2]處的Self變更為TMyForm。則[Show 1]顯示正常,[Show 2]顯示不正常。
//說明:當類的函數被執行時,寄存器eax保存的是當前類的地址。所以,TMethod.Data中保存的應該是將來執行TMethod.Code函數時,賦給eax的值,即類對象指針。
//又因為[Show 1]中,不需要eax中類對象指針,所以可以正常執行。
//**************** 修改 二
//函數地址讀取部分
procedure TMyForm.Button1OnClick(Sender: TObject);
var
?vMethod: TMethod;
?vEvent: TNotifyEvent;
begin
?FMyText := 'Hello Joy!';
?vEvent := MyClick;
?vMethod.Code := @vEvent;??????????????????????????? //************ Code 1
?vMethod.Data := Self;?????????????????????????????? //************ Code 2
?SetMethodProp(Button2, 'OnClick', vMethod);
end;
//其中,MyClick可以定義為私有函數。
//說明:vMethod只是要記錄一個類的函數地址和類對象的地址。MethodAddress函數也只是通過函數名稱進行函數地址的讀取而已。
?
?2003-6-18 12:48:38??? 不通過匯編訪問 [VMT]
VMT:Virtual Method Table
//訪問VMT信息
e.g.
procedure TForm1.Button1Click(Sender: TObject);
var
?vpt: Pointer;
?vMethod: TMethod;
begin
?vMethod.Code := TForm.MethodAddress('MyClick');
?vMethod.Data := Self;
?if vMethod.Code = nil then ShowMessage('Error!');
?vPt := Pointer(TListBox);?
?Integer(vPt) := Integer(vPt) + vmtTypeInfo; //在Delphi幫助中說明,根據偏移量可以取得ClassInfo。或者可以參考,TObject.ClassInfo的定義。
?SetMethodProp(ListBox2, GetPropInfo(pTypeInfo(vPt^), 'OnClick'), vMethod);
end;
//其中,類的類到底如何提取?
?function TObject.ClassType: TClass;
?begin
???? mov eax, [eax]
?end;
//上面的語句可以翻譯成
?function TObject.ClassType: TClass;
?begin?
??? Result := TClass(Pointer(Self)^);
?end;
//所以上面的例子也可以改為
procedure TForm1.Button1Click(Sender: TObject);
var
?vpt: Pointer;
?vMethod: TMethod;
begin
?vMethod.Code := TForm.MethodAddress('MyClick');
?vMethod.Data := Self;
?if vMethod.Code = nil then ShowMessage('Error!');
?vPt := Pointer(Pointer(ListBox2)^);? //取得ClassType的指針
?Integer(vPt) := Integer(vPt) + vmtTypeInfo;
?SetMethodProp(ListBox2, GetPropInfo(pTypeInfo(vPt^), 'OnClick'), vMethod);
end;
{仿照上面的例子,我們可以訪問所有VMT入口地址,并取得相應的信息}
?
?2003-6-21 11:24:32??? 使用匯編實現遠程函數調用
{ No. 19 }
//如何通過指針,調用類函數中定義的函數(如下面的:MyFar)?
//如果我們只傳遞函數指針,然后調用函數的話,我們會發現,在MyFar中不能訪問當前類對象的變量FMyText。如果嵌入一段匯編,將當前位置壓入棧,然后再調用此函數,則就可以象類函數一樣,在其中訪問類的變量了。
e.g.
type
?pMyRec = ^TMyRec;
?TMyRec = record
?? rStr: String;
?? rInt: Integer;
?end;
procedure MyProc(APro: Pointer);
var
?CallerBp: Cardinal;
?MyRec: TMyRec;
?vPt: Pointer;
begin
?MyRec.rStr := 'MyRec.rStr';
?MyRec.rInt := 'MyRec.rInt';
?vPt := Pointer(@MyRec);
?asm
?? mov eax, [ebp]
?? mov CallerBp, eax
?? mov eax, vpt
?? Push CallerBp
?end;
end;
procedure TMyTemp.SetText(aText: String);
?procedure MyFar(aRec: TMyRec);
?begin
??? ShowMessage(Format('%s_%sd_%s', [Self.FmyText, aRec.rStr, aRec.rInt]);
?end;
begin
?Self.FMyText := 'JoyYuan';
?MyProc(Addr(MyFar));
end;
{說明:具體例子可以參考:Grids單元中,TSparsePointerArray.ForAll的實現。
}
?
?2003-6-21 11:47:21??? 類函數地址
{ No. 20 }
測試結果列舉:也是測試方法和測試思維經歷的過程。
結果一:Button1.CanFocus與Button2.CanFocus的地址相同
結果二:Button1.CanFocus與Form1.CanFocus的地址相同,也等同于ListBox1.CanFocus
結果三:當在TForm1窗體類中,重載Form1.CanFocus后,Button1.CanFocus與Form1.CanFocus的地址不相同
?結論:如果沒有重載父類的虛函數,則訪問時,直接得到并訪問父類的函數。所以,TButton, TListBox, TForm默認都訪問的是TWinControl的CanFocus。所以函數地址相同。
結果四:定義事件類型
Type
?TMyEvent1 = Function(): Boolean of Object;
?TMyEvent2 = function(): Boolean;
?得結果:Sizeof(TMyEvent1) = 8;? Sizeof(TMyEven2) = 4;
?結論:類函數類型,保存的室兩個指針的內容,見TMethod中,Code 和 Data;既一個函數指針,一個對象指針。
驗證測試例子一:
var
?vTestEvent: TNotifyEvent;
begin
?Pointer((@@vTestEvent)^) := @TForm1.MyClick; //或者通過:TForm1.MethodAddress('MyClick') 方式取函數地址
?Pointer(Pointer(Integer(@@vTestEvent) + 4)^) := Pointer(Self);
?vTestEvent(nil); //效果和執行 Self.MyClick一樣。
end;
驗證測試例子二:
var
?vMethod: TMethod;
begin
?vMethod.Code := TForm1.MethodAddress('MyClick');
?vMethod.Data := Self;
?TNotifyEvent(vMethod)(nil);//效果和執行 Self.MyClick一樣。
end;
?
?
文章作者:大富翁的joyyuan97
?
出處:http://www.delphibbs.com/keylife/iblog_show.asp?xid=1134
?
總結
以上是生活随笔為你收集整理的delphi程序设计之底层原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Delphi中methodaddress
- 下一篇: 也可以让生命发出耀眼的飞鸽传书光芒