程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> VCL源碼分析方法論

VCL源碼分析方法論

編輯:Delphi
最近一段時間似乎流行源碼分析:)我也來談談在過去一段時間裡對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屬性和其它一些方法/函數。
  希望我這篇‘散文’能給大家理出點頭緒:)
  
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved