vcl源碼分析方法論

來源:互聯網
上載者:User

vcl源碼分析方法論

最近一段時間似乎流行源碼分析:)我也來談談在過去一段時間裡對VCL源碼的分析方法方面的一點體會,本文將不探討VCL類庫的構架和設計模式方面的東本,只是以我們常見的控制項屬性/方法的實現過程作簡單的說明,希望對初學者有所協助

VCL分析方法
例:TButton.Caption屬性的由來
(本文僅以此獻給DELPHI初學者)
   用過一段時間DELPHI的朋友,都會對VCL源碼感興趣。本人也常常在各大論壇見到一些網友研究討論過關於VCL源碼的貼子。不過,很多網友很努力的想看懂,可最後還是半途而廢,因為他們總是理不出個頭緒、看得雲裡霧裡。筆者我也有看源碼的習慣,沒事的時候就點點滑鼠右鍵,總是希望得到一些僥倖的收穫和開發技巧。
   不過萬事都得先有個基本前題,就像人上學的過程一樣(這裡指正常人)要按部就班的來,一般不可能小學一畢業就直接去念大學,除非他(她)是個天才或經過特別培訓。所以各位GGJJDDMM,看VCL源碼也是有個基本前題的,首先你得熟悉WIN32 API/SDK,如果你說不知道的話,可以參考書籍《Programming Windows》(中文名《WINDOWS 程式設計》)。其次是你應當對Object Pascal比較熟悉,或者你曾經對DELPHI的組件進行過擴充(做過組件開發),那麼我相信你對Object Pascal已經熟悉。不熟也不要緊,DELPHI的線上協助就有對Object Pascal的講述,如果英文太差也不要緊,網上也有很多熱心網友翻譯過來的中文協助和語言參考書。
呵呵,本人寫技術文章就像在寫散文:)
   言歸正傳,我們這篇文章的主題是對VCL源碼的分析,分析當然有一個分析方法的問題,總不能隨便開啟一個來源程式,逮著一個函數就分析一個函數吧:)所以我們也應該有選擇,有目的的分析。
想想我們每天編碼時都會遇到的屬性有哪些?呵呵,NAME,CAPTION,VISIBLE,還有一些控制項的TEXT(如EDIT1.TEXT)。那麼我們就以控制項的CAPTION來分析吧。
當然不是每個控制項都有CAPTION屬性的,我們這裡就用TButton類的Caption屬性進行分析。
   開啟每天我們都會使用的DELPHI,在FORM表單上放一個按鈕,得到一個Button1的按鈕控制項,按F12打天來源程式,有沒有找到這段代碼呢:
Button1: TButton;
對了,在TButton上點擊滑鼠右鍵,在彈出的操作功能表中選擇第一項Find Declaration,找到TButton類的定義,如下所示:
 TButton = class(TButtonControl)
 private
   FDefault: Boolean;
   FCancel: Boolean;
   FActive: Boolean;
   FModalResult: TModalResult;
   procedure SetDefault(Value: Boolean);
。。。。。。

   原來TButton繼承於TButtonControl類,呵呵:)
在左邊的對象視窗(Exploring Unit.pas視窗)中找到TButton的CAPTION屬性,如:

   雙擊CAPTION屬性,找到定義CAPTION屬性的源碼,大家可能發現什麼都沒有,只有一個   
   property Caption;
   呵呵,寫過組件的朋友都知道,按理Caption屬性應該有讀/寫文本的方法啊?在哪裡去了呢,呵呵,這裡沒有出現,當然應該在它的父類裡了(這裡只是申明Caption出來的地方),我們順著剛才的方法繼續在TButtonControl,發現也沒有,最終我們在TControl類裡找到了這個CAPTION,至於為什麼是protected成員,我就不多說了:
 protected
   procedure ActionChange(Sender: TObject; CheckDefaults: Boolean); dynamic;
   procedure AdjustSize; dynamic;
   procedure AssignTo(Dest: TPersistent); override;
   procedure BeginAutoDrag; dynamic;
   function CanResize(var NewWidth, NewHeight: Integer): Boolean; virtual;
   function CanAutoSize(var NewWidth, NewHeight: Integer): Boolean; virtual;
   procedure Changed;
   procedure ChangeScale(M, D: Integer); dynamic;
。。。。。。
   property Caption: TCaption read GetText write SetText stored IsCaptionStored;
   看看GetText、SetText就是操作文字屬性的函數了,我們找到GetText、SetText定義如下:
function GetText: TCaption;
procedure SetText(const Value: TCaption);
還有TCaption,它的定義居然是一個自訂類型:
TCaption = type string;
   說明GetText傳回值和SetText的調用參數本來也就是一個string型的:)

   下面我們來看看GetText源碼:
function TControl.GetText: TCaption;
var
 Len: Integer;
begin
 Len := GetTextLen;//得到文本長度
 SetString(Result, PChar(nil), Len);// 設定Result返回以Len指定的長度
 if Len <> 0 then GetTextBuf(Pointer(Result), Len + 1);//長度不為空白,Result得到文本資料
end;

   如果不明白GetTextBuf的用法,看看如下的代碼:
procedure TForm1.Button1Click(Sender: TObject);
var
 Buffer: PChar;
 Size: Byte;
begin
 Size := Edit1.GetTextLen;   //得到EDIT1的文本長
 Inc(Size);               
 GetMem(Buffer, Size);          //建立EDIT1文本長度大小的緩衝空間
 Edit1.GetTextBuf(Buffer,Size);  //由緩衝得到文本,Buffer裡的值就是Edit1.Text
 Edit2.Text := StrPas(Buffer);   //Buffer轉換為PASCAL字元類型資料
 FreeMem(Buffer, Size);      //釋放記憶體
end;
以上程式的行為同以下程式相當:
procedure TForm1.Button1Click(Sender: TObject);
begin
 Edit2.Text := Edit1.Text;
end;

   回到GetText函數,其中GetTextLen的作用是得到文本長度,GetTextBuf得到文本資料。
   SetText就更簡單了,定義如下:
procedure TControl.SetText(const Value: TCaption);
begin
 if GetText <> Value then SetTextBuf(PChar(Value));
end;
   意思是如果設定的Value與原來的不同,則重新設定緩衝文本。

 

   為了更深入VCL底部,我們再看看GetTextLen如何?的(其實SetTextBuf和GetTextLen的實現過程相似):
function TControl.GetTextLen: Integer;
begin
 Result := Perform(WM_GETTEXTLENGTH, 0, 0);//WM_派發的是WINDOWS標準訊息
end;
   看到這裡想必大家都明白了,如果還不明白(沒用過Perform),我看再看看Perform,它到底做了什麼:
function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;
var
 Message: TMessage;
Begin
{你的訊息賦予TMessage }
 Message.Msg := Msg; ;
 Message.WParam := WParam;
 Message.LParam := LParam;
Message.Result := 0;//0表示返回不處理
 if Self <> nil then WindowProc(Message);//不為空白,將訊息交給TControl的視窗過程WindowProc處理
 Result := Message.Result;//返回結果
end;
   這裡主要再看看WindowProc做了什麼,TControl裡面WindowProc是這樣定義的:
property WindowProc: TWndMethod read FWindowProc write FWindowProc;
在TControl的Create函數中:
constructor TControl.Create(AOwner: TComponent);
begin
 inherited Create(AOwner);
 FWindowProc := WndProc;
。。。。。。
   可見我們還要找到TControl 的WndProc過程才能明白究竟,
WndProc流程定義如下:
procedure WndProc(var Message: TMessage); override;
   實現:
procedure TControl.WndProc(var Message: TMessage);
var
 Form: TCustomForm;
 KeyState: TKeyboardState; 
 WheelMsg: TCMMouseWheel;
begin
 if (csDesigning in ComponentState) then
 begin
   Form := GetParentForm(Self);
   if (Form <> nil) and (Form.Designer <> nil) and
     Form.Designer.IsDesignMsg(Self, Message) then Exit
 end;
 if (Message.Msg >= WM_KEYFIRST) and (Message.Msg <= WM_KEYLAST) then
 begin
   Form := GetParentForm(Self);
   if (Form <> nil) and Form.WantChildKey(Self, Message) then Exit;
 end
 else if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then
 begin
   if not (csDoubleClicks in ControlStyle) then
     case Message.Msg of
       WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK:
         Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
     end;
   case Message.Msg of
     WM_MOUSEMOVE: Application.HintMouseMessage(Self, Message);
     WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:
       begin
         if FDragMode = dmAutomatic then
         begin
           BeginAutoDrag;
           Exit;
         end;
         Include(FControlState, csLButtonDown);
       end;
     WM_LBUTTONUP:
       Exclude(FControlState, csLButtonDown);
   else
     with Mouse do
       if WheelPresent and (RegWheelMessage <> 0) and
         (Message.Msg = RegWheelMessage) then
       begin
         GetKeyboardState(KeyState);
         with WheelMsg do
         begin
           Msg := Message.Msg;
           ShiftState := KeyboardStateToShiftState(KeyState);
           WheelDelta := Message.WParam;
           Pos := TSmallPoint(Message.LParam);
         end;
         MouseWheelHandler(TMessage(WheelMsg));
         Exit;
       end;
   end;
 end
 else if Message.Msg = CM_VISIBLECHANGED then
   with Message do
     SendDockNotification(Msg, WParam, LParam);
 Dispatch(Message);//派發訊息
end;
   這裡主要講講Dispatch方法,它根據傳入的訊息調用訊息的控制代碼方法,如果在組件類和它的父類都沒有找到訊息的處理控制代碼,Dispatch方法便會調用Defaulthandler(預設的訊息處理方法),如下:
procedure TObject.Dispatch(var Message);
asm
   PUSH    ESI
   MOV     SI,[EDX]
   OR      SI,SI
   JE      @@default
   CMP     SI,0C000H
   JAE     @@default
   PUSH    EAX
   MOV     EAX,[EAX]
   CALL    GetDynaMethod
   POP     EAX
   JE      @@default
   MOV     ECX,ESI
   POP     ESI
   JMP     ECX

@@default:
   POP     ESI
   MOV     ECX,[EAX]
   JMP     DWORD PTR [ECX] + VMTOFFSET TObject.DefaultHandler//調用預設的訊息處理方法
end;
   而預設的訊息處理如下,在SYSTEM.PAS單元裡:
procedure TObject.DefaultHandler(var Message);
begin
end;
     由以上代碼看好像是沒有任何處理過程,跟蹤Object.DefaultHandler的彙編執行動作call dword ptr[ecx-$10],即調用Object.DefaultHandle,看看做何處理:
{Object.DefaultHandle}
Ret
Lea eax,[eax+$00]
即一個返回處理!

從最表面的Button.caption,我們走到了編譯器層,可見所有東西都能找到它固有的原點!以caption的分析為基礎,我們可以繼續分析name屬性和其它一些方法/函數。
希望我這篇‘散文’能給大家理出點頭緒:)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.