delphi程序设计之底层原理
雖然用delphi也有7,8年了,但大部分時(shí)間還是用在系統(tǒng)的架構(gòu)上,對(duì)delphi底層還是一知半解,今天在網(wǎng)上看到一篇文章寫得很好,雖然是07年的,但仍有借鑒的價(jià)值。
現(xiàn)摘錄如下:
Delphi程序設(shè)計(jì)之--經(jīng)驗(yàn)技巧
這些日子太忙了,今天把剩下的部分貼完,希望對(duì)大家有用。看過前一篇的都知道此文的作者和出處,我就不詳細(xì)說了。
?
{ No. 16 }
//對(duì)于記錄類型Record的分析。
實(shí)例:
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、記錄類型互相轉(zhuǎn)換時(shí),必須保證基礎(chǔ)Record類型,數(shù)據(jù)大小Sizeof應(yīng)小于或等于擴(kuò)展類型。保證轉(zhuǎn)換后的記錄類型對(duì)象的數(shù)據(jù)訪問合法正確。
2、在Delphi中,使用記錄類型互相轉(zhuǎn)換最為平凡的就是在消息Record的實(shí)現(xiàn)上了。在Delphi中定義了若干于TMessage可同時(shí)描述消息接受信息的Record,如:
?TWMKey = packed record
?? Msg: Cardinal;
?? CharCode: Word;
?? Unused: Word;
?? KeyData: Longint;
?? Result: Longint;
?end;
?當(dāng)需要接受KeyDown和KeyUp的消息時(shí),我們即可以使用TMessage也可以使用TWMKey作為消息接收的參數(shù)類型。因?yàn)镈elphi為我們提供了若干便利的消息類型,所以我們?cè)谑褂孟⑻幚韱柺罆r(shí)就不會(huì)象VC中那樣繁瑣和易錯(cuò)了。
3、記錄類型的使用還提供了一個(gè)不同語言間數(shù)據(jù)信息封裝訪問的途徑。在不同語言間使用記錄類型和記錄類型指針時(shí),應(yīng)注意內(nèi)部定義的變量的類型匹配問題。
記錄類型的本質(zhì)測(cè)試研究:
更改上面例子的實(shí)現(xiàn)部分,測(cè)試:
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;
提示信息于開始例子相同,則推測(cè):
1、Record類型中定義的數(shù)據(jù)是在一個(gè)連續(xù)空間中保存的
2、當(dāng)定義函數(shù)時(shí),如果考慮到函數(shù)處理的信息可能在后續(xù)版本中,需要擴(kuò)充則可以使用記錄變量的方式傳遞參數(shù)。當(dāng)擴(kuò)充函數(shù)時(shí)只需將記錄變量根據(jù)此記錄的版本號(hào)轉(zhuǎn)換為對(duì)應(yīng)的記錄類型變量進(jìn)行訪問即可。具體實(shí)例可以參考Windows API函數(shù)的版本升級(jí)及擴(kuò)展情況。
}
2003-6-17 19:52:00???
?2003-6-17 22:22:02??? 事件類型屬性,通過屬性賦值函數(shù)操作
{ No. 17 }
{在TypInfo單元中,有若干函數(shù)可以讓我們操作Dephi管理的類的VMT。通過,屬性名稱和對(duì)象VTM直接訪問或改變屬性值。
公共的屬性訪問函數(shù):GetPropValue;公共的屬性設(shè)置函數(shù):SetPropValue。其中,對(duì)事件屬性信息的讀取可以使用GetPropValue,但是卻不能通過SetPropValue給事件屬性賦值。
解決方案:使用SetMethodProp給控件屬性賦值。procedure SetMethodProp(Instance: TObject; const PropName: string; const Value: TMethod); overload;其中,TMethod 用以描述操作函數(shù)。
?TMethod = record
?? Code,? //函數(shù)地址;可以通過類函數(shù)MethodAddress,取得函數(shù)地址。其中,只有聲明在Published段的函數(shù)才能通過MethodAddress訪問。
?? Data: Pointer; //對(duì)象地址
?end;
}
//**************** 正常使用
type
?TMyForm = class(TForm)
? ...
?private
?? FMyText: String;
?published
?? procedure MyClick(Sender: TObject);
?end;
?
//窗體中,按鈕事件;實(shí)現(xiàn)動(dòng)態(tài)分配另一個(gè)按鈕的事件的方法
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]顯示不正常。
//說明:當(dāng)類的函數(shù)被執(zhí)行時(shí),寄存器eax保存的是當(dāng)前類的地址。所以,TMethod.Data中保存的應(yīng)該是將來執(zhí)行TMethod.Code函數(shù)時(shí),賦給eax的值,即類對(duì)象指針。
//又因?yàn)閇Show 1]中,不需要eax中類對(duì)象指針,所以可以正常執(zhí)行。
//**************** 修改 二
//函數(shù)地址讀取部分
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可以定義為私有函數(shù)。
//說明:vMethod只是要記錄一個(gè)類的函數(shù)地址和類對(duì)象的地址。MethodAddress函數(shù)也只是通過函數(shù)名稱進(jìn)行函數(shù)地址的讀取而已。
?
?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幫助中說明,根據(jù)偏移量可以取得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入口地址,并取得相應(yīng)的信息}
?
?2003-6-21 11:24:32??? 使用匯編實(shí)現(xiàn)遠(yuǎn)程函數(shù)調(diào)用
{ No. 19 }
//如何通過指針,調(diào)用類函數(shù)中定義的函數(shù)(如下面的:MyFar)?
//如果我們只傳遞函數(shù)指針,然后調(diào)用函數(shù)的話,我們會(huì)發(fā)現(xiàn),在MyFar中不能訪問當(dāng)前類對(duì)象的變量FMyText。如果嵌入一段匯編,將當(dāng)前位置壓入棧,然后再調(diào)用此函數(shù),則就可以象類函數(shù)一樣,在其中訪問類的變量了。
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的實(shí)現(xiàn)。
}
?
?2003-6-21 11:47:21??? 類函數(shù)地址
{ No. 20 }
測(cè)試結(jié)果列舉:也是測(cè)試方法和測(cè)試思維經(jīng)歷的過程。
結(jié)果一:Button1.CanFocus與Button2.CanFocus的地址相同
結(jié)果二:Button1.CanFocus與Form1.CanFocus的地址相同,也等同于ListBox1.CanFocus
結(jié)果三:當(dāng)在TForm1窗體類中,重載Form1.CanFocus后,Button1.CanFocus與Form1.CanFocus的地址不相同
?結(jié)論:如果沒有重載父類的虛函數(shù),則訪問時(shí),直接得到并訪問父類的函數(shù)。所以,TButton, TListBox, TForm默認(rèn)都訪問的是TWinControl的CanFocus。所以函數(shù)地址相同。
結(jié)果四:定義事件類型
Type
?TMyEvent1 = Function(): Boolean of Object;
?TMyEvent2 = function(): Boolean;
?得結(jié)果:Sizeof(TMyEvent1) = 8;? Sizeof(TMyEven2) = 4;
?結(jié)論:類函數(shù)類型,保存的室兩個(gè)指針的內(nèi)容,見TMethod中,Code 和 Data;既一個(gè)函數(shù)指針,一個(gè)對(duì)象指針。
驗(yàn)證測(cè)試?yán)右?#xff1a;
var
?vTestEvent: TNotifyEvent;
begin
?Pointer((@@vTestEvent)^) := @TForm1.MyClick; //或者通過:TForm1.MethodAddress('MyClick') 方式取函數(shù)地址
?Pointer(Pointer(Integer(@@vTestEvent) + 4)^) := Pointer(Self);
?vTestEvent(nil); //效果和執(zhí)行 Self.MyClick一樣。
end;
驗(yàn)證測(cè)試?yán)佣?#xff1a;
var
?vMethod: TMethod;
begin
?vMethod.Code := TForm1.MethodAddress('MyClick');
?vMethod.Data := Self;
?TNotifyEvent(vMethod)(nil);//效果和執(zhí)行 Self.MyClick一樣。
end;
?
?
文章作者:大富翁的joyyuan97
?
出處:http://www.delphibbs.com/keylife/iblog_show.asp?xid=1134
?
總結(jié)
以上是生活随笔為你收集整理的delphi程序设计之底层原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Delphi中methodaddress
- 下一篇: 也可以让生命发出耀眼的飞鸽传书光芒