Delphi深度探索-CodeSite應用指南
Delphi雖然為我們提供極其強大的調試功能,查找Bug仍然是一項艱鉅的工作,通常我們編寫代碼和調試代碼的所消耗的時間是大致相同的,甚至有可能更多。為了減少無謂的時間和精力的浪費,有時我們還是需要專業調試工具的幫助來提高鎖定Bug的效率。本文中我們將介紹著名的調試工具CodeSite PRo 2.0(它獲得了2000年度Delphi Informant讀者選擇的最佳調試工具獎的第二名)。它的官方網址是www.raize.com。 CodeSite的主要功能是可以讓開發者使用程式碼來發送運行時的詳細資訊到特殊的接收器,以便於進一步分析。更精確的說透過CodeSite實現的TCodeSite類,我們可以打包並發送運行時的信息給CodeSite Dispatcher(CodeSite的消息分發器),它可以路由這些消息到一個或多個接收器來察看。預設的資訊接收器是CodeSite Viewer(訊息察看器)。 CodeSite的效率體現在它不同於簡單的顯示訊息的對話框或設定斷點來檢查變量,它更類似於Delphi自帶的事件日誌功能(Event Log),當然毫無疑問它要比Event Log強大的多,它的訊息是可持續的,也就是可以保存的,便於回溯分析。 在介紹CodeSite的具體使用之前,我們先來看看它的三個組成部分。 CodeSite物件如前面所提到的,從運行的應用程式中向外發送CodeSite訊息是透過使用TCodeSite類別(定義在CSIntf單元中)的一個實例來完成的,我們只要簡單的呼叫TCodeSite類別的方法就可以把訊息發送給CodeSite Dispatcher。例如,可以使用物件的SendMsg方法來傳送一個簡單的字串訊息。 TCodeSite 物件實作了大量的方法來支援各種類型的資訊發送而無須任何資料轉換,例如物件的SendObject方法有兩個參數:一個是訊息字串,一個是對物件實例的引用,這個方法會取得對象所有published的屬性,然後把這些屬性的資訊打包進CodeSite的訊息中。 CodeSite Dispatcher 大多數情況下,CodeSite Dispatcher會安靜的運作在系統的托盤區。它的唯一功能是路由從各個TCodeSite物件發送的CodeSite的訊息到它們的目的地。缺省時,CodeSite訊息都會發給CodeSite Viewer。我們甚至不需要啟動CodeSite Dispatcher,因為它會被TCodeSite等物件自動啟動。 TCodeSite 類別定義了一個DestinationDetails屬性,它允許開發者設定發送的CodeSite訊息是如何被CodeSite Dispatcher路由到不同目的地,例如日誌檔案。但通常沒有必要修改這個屬性。 CodeSite Viewer 雖然CodeSite 支援發送訊息到不同的目標,但決大多數情況下CodeSite Viewer是主要的發送目標。即使是傳送到其他目標,例如日誌檔案或另一台機器,CodeSite Viewer仍然是察看分析訊息的主要工具。 CodeSite Viewer由下面四個面板構成:訊息列表,訊息察看器,呼叫堆疊和Scratch面板。 CodeSite Viewer的主要工作區是Message列表,它用來顯示發送給Viewer的全部訊息或是從日誌檔案載入的訊息。 訊息察看器用來察看同訊息關聯的額外資訊。例如如果目前的訊息是由SendObject方法發送的話,訊息察看器就會顯示物件全部的publised屬性目前值。 呼叫堆疊面板會根據csmEnterMethod訊息顯示一個堆疊視圖。 Scratch面板則是用來顯示非可持續的資訊的。當我們想追蹤某些訊息,但又不想在訊息日誌中記錄它們的時候,例如當我們想察看象滑鼠當前位置這類大量的並重複的訊息時,Scratch面板是非常有用的。這時我們可以使用TCodeSite物件的WritePoint方法,並指定Line ID參數以便指定用來容納滑鼠資訊的scratch面板行數。 下面就讓我們用一個簡單的範例來示範如何從程式中傳送訊息給 CodeSite Viewer:(1)建立一個新的項目,然後切換元件面板到CodeSite頁面(CodeSite安裝後會在系統中安裝兩個元件TCSGlobalObject和TCSObject)。選擇TCSGlobalObject元件然後放到窗體上。 TCSGlobalObject元件提供了設計時對全域TCodeSite物件的交互作用(全域TCodeSite是在CSInft單元中被初始化的)。 (2)新增一個按鈕,然後在它的OnClick事件中寫下如下程式碼: //CodeSite就是全域的TCodeSite物件CodeSite.SendMsg('CodeSite的第一個訊息');(3)編譯並執行這個簡單的程式.運行後點擊按鈕,CodeSite Dispatcher和CodeSite Viewer將會運行。同時在CodeSite Viewer的訊息清單中將會看到程式發出的訊息(注意:我們沒有必要在程式執行前啟動CodeSite Dispatcher和CodeSite Viewer,因為TCodeSite 物件在需要傳送訊息的時候會自動啟動它們的)。運行結果如下圖4.38所示:
(4)接下來,停止程序,在OnClick事件處理過程中添加下面代碼: CodeSite.SendObject('Form1', Form1 ); (5)重新編譯運行程序,再點按鈕一次,這回你會在CodeSite Viewer中看到兩條訊息。其中Form1對應的訊息包括Form1的物件資訊。 (6)為了看到Form1的相關聯的物件訊息,選擇CodeSite Viewer的選單指令View|Inspector會在訊息列表右側顯示一個新的面板,Form1的published屬性都被顯示在其中,如下圖4.39所示:
(7)再次停止程序,然後修改OnClick過程中代碼如下: CodeSite.EnterMethod('Button1Click'); CodeSite.SendMsg('CodeSite的第一條訊息'); CodeSite.SendObject('Form1', Form1 ); CodeSite .ExitMethod('Button1Click' ); (8)這次我們再執行程式點選按鈕後,就會看到「CodeSite的第一則訊息」和「Form1」的訊息被縮排在「Button1Click」訊息之間,如下圖4.40所示:
透過新增EnterMethod和ExitMethod方法的調用,我們可以產生一個日誌來記錄方法何時被調用。 看過例子之後,我們會發現CodeSite的功能是非常強大的,我們只要簡單的在程式中加入幾個語句就可以產生非常詳細的信息,並透過CodeSite Viewer以生動的圖表表現出來。接下來,我們再來談談CodeSite的高階應用技術。 發送訊息到日誌檔案每個程式或多或少都會有Bug,不在這時發生,也會在那時發生,短時間內不發生,很長時間就可能發作,有時反覆出現,有時非常偶然的才能被發現。如果一個人告訴你他寫的程式在任何時候都沒有任何問題,他一定是在說謊。正是由於Bug的偶然性和隱蔽性,就使得我們往往很難重複使用者提交的Bug,這就給我們調試程式並找到問題的原因產生了極大的障礙,而CodeSite能夠發送訊息到日誌檔案的特性就使得使用者回報Bug變得更容易,他們只要把運行時產生的資訊文件提交就可以了。相應的我們調試程式的工作也會變得更輕鬆,我們可以使用CodeSite Viewer來直觀的分析錯誤發生的原因和位置。 若要改變訊息傳送的目標,我們可以透過設定TCodeSite 物件的DestinationDetails屬性來實現。這項功能要求客戶的機器上必須安裝了CodeSite Dispatcher,它屬於CodeSite中可自由分發的部分。下面的要講具體過程仍然是基於前面講過的例子: (1)在窗體的OnCreate事件中加入下面程式碼: CodeSite.DestinationDetails := 'File[Path=C:/FirstLog.csl]'; (2 )編譯並餘興程序,這回我們在點擊按鈕後,訊息就不再被傳送給CodeSite Viewer而是傳送到C盤的FirstLog.csl檔案中。 (3)使用CodeSite Viewer載入FirstLog.csl文件,這回我們就像先前一樣察看被儲存的CodeSite訊息了。 (4)如果我們想把訊息同時傳送到CodeSite Viewer和日誌檔案的話,只修改前面的程式碼為: CodeSite.DestinationDetails := 'Viewer,File[Path=C:/FirstLog.csl]'; 傳送使用者自訂的資料雖然TCodeSite 類別提供了大量的處理不同資料類型的方法,但有時我們可能會需要發送某種自訂格式的資料資訊。為此,TCodeSite 類別定義了SendCustomData 方法,它支援傳送任意的資料類型,並將根據一個自訂的格式器來格式化資料以便CodeSite Viewer可以正確的顯示資料。 首先我們要建立一個TCSFormatter 物件的子類,然後重載物件的FormatData,InspectorType和TypeName方法。然後呼叫CodeSite物件管理器物件CSObjectManager的來註冊新的TCSFormatter子類別。此外,我們還需要呼叫RegisterCustomFormat方法來註冊一個新的訊息類型。 以下是一個實際應用的例子,在單元CSEmployee.pas中實作了一個TCSEmployeeRecord記錄類型的自訂格式器: unit CSEmployee; interface uses Windows, Graphics, CSIntf; const csmEmployeeSummary = csmUser + 1; csmEmployeeDetails = cUsermployeeSummary = csmUser + 1; csmEmployeeDetails = cUsermployeeSummary = csmUser + 1; csmEmployeeDetails = cUsermployeeSummary = csmUser + 1; csmEmp. Uses部分加入CSIntf 單元的引用。第二步是為每個格式器定義新的CodeSite訊息類型常數,上面我們定義了兩個常數,注意常數應該大於csmUser,但不能大過32,000。 type TCSEmployee = record LastName: string; FirstName: string; Address: string; City: string; State: string; ZipCode: string; PhoneNumber: string; HireDate: TDateTime; Salary: Currency; VacationDays: Integer; Boolean; end;上面的記錄就是我們要傳送的自訂的資料類型。 TCSEmployeeSummaryFormatter = class( TCSFormatter ) public function InspectorType: TCSInspectorType; override; procedure FormatData( var Data ); override; function TypeName: string; 問題; override; procedure FormatData( var Data ); override; function TypeName: string; override; end; 上面是兩個客製化的格式器類別的定義。第一個格式器將把TCSEmployee 記錄格式化為一個文字格式,第二個格式化器將把TCSEmployee 記錄格式化為網格樣式。 implementation uses SysUtils; {=========================================} {== TCSEmployeeSummaryFormatter Methods ==} {=========================================} function TCSEmployeeSummaryFormatter .InspectorType: TCSInspectorType; begin Result := itStockStringList; end; 實作一個自訂的格式化器的第一步是確定哪種類型的內建察看器將被用來察看格式化後的數據,這裡使用的是字串列表察看器。察看器類型將會被FormatData方法所使用。 procedure TCSEmployeeSummaryFormatter.FormatData( var Data ); var EmpRec: TCSEmployee; begin EmpRec := TCSEmployee( Data ); AddLine( EmpRec.FirstName + ' ' + EmpRec.LastName ); AddLine( EmpRec.FirstName + ' ' + EmpRec.LastName ); AddLine Empc. ', ' + EmpRec.State + ' ' + EmpRec.ZipCode ); AddLine( '' ); AddLine( 'Phone: ' + EmpRec.PhoneNumber ); AddLine( 'Hire Date: ' + DateToStr( 'SalRec.HireDate ) ); AddLine( 'Salary : ' + Format( '%m', [ EmpRec.Salary ] ) ); AddLine( '' ); AddLine( 'Vacation Days: ' + IntToStr( EmpRec.VacationDays ) ); AddLine( 'Sick Days: ' + IntToStr( EmpRec.SickDays ) ); if EmpRec.Manager then AddLine( ': Yes' ) else AddLine( 'Manager: No' ); end; FormatData方法是核心部分,注意傳遞給FormatData方法的Data參數是一個無類型的可變參數。這意味著這個參數可以是任何資料類型的,透過格式註冊過程,我們可以確保強制類型對應為自訂的資料記錄,而不會發生轉換錯誤。 轉換資料型別後,我們就可以對資料進行格式化了,這裡使用TCSFormatter 基底類別的AddLine方法在字串間加入分割線來進行格式化。 function TCSEmployeeSummaryFormatter.TypeName: string; begin Result := 'TCSEmployee'; end; TypeName方法的重載是可選擇性的,但通常我們可以用它來傳回顯示在訊息清單中的字串。 {=========================================} {== TCSEmployeeDetailsFormatter Methods == } {=========================================} function TCSEmployeeDetailsFormatter.InspectorType: TCSInspectorType ; begin Result := itStockGrid; end;對於employeedetails格式器來說,命名網格察看器會被用來察看資料資訊: procedure TCSEmployeeDetailsFormatter.FormatData( var Data ); var EmpRec: TCSEmployee; begin EmpRec := TCSEmployee( Data ); NameValuePair( 'LastName' .LastName ); AddNameValuePair( 'FirstName', EmpRec.FirstName ); AddNameValuePair( 'Address', EmpRec.Address ); AddNameValuePair( 'City', EmpRec.City ); AddNameValuePair( 'State', EmpRec.State ); AddNameValuePair( 'ZipCode'air, EmpReipCode'airc.ZipcA); ( 'PhoneNumber', EmpRec.PhoneNumber ); AddNameValuePair( 'HireDate', EmpRec.HireDate ); AddNameValuePair( 'Salary', Format( '%m', [ EmpRec.Salary ] ) ); AddNameNameValuePair( 'VacationDays', EmpRec.Va Addaira ); 'SickDays', EmpRec.SickDays ); AddNameValuePair( 'Manager', EmpRec.Manager ); end; 這裡為了在網格察看器中格式化數據,我們使用AddNameValuePair方法來實作。 function TCSEmployeeDetailsFormatter.TypeName: string; begin Result := 'TCSEmployee'; end; 下面兩個過程是用來封裝對SendCustomData方法的呼叫的,這裡對全域的TCodeSite物件實例CodeSiteite進行了呼叫: {===== ================} {== Support Methods ==} {=====================} procedure CSSendEmployeeSummary( const Msg: string; EmpRec: TCSEmployee ); begin CodeSite.SendCustomData( csmEmployeeSummary, Msg, EmpRec ); end; procedure CSS CodeSite.SendCustomData( csmEmployeeDetails, Msg, EmpRec ); end; 最後,不要忘了呼叫CSObjectManager.RegisterCustomFormatter方法把格式器註冊到CodeSite物件管理器中。 initialization CSObjectManager.RegisterCustomFormatter( csmEmployeeSummary, TCSEmployeeSummaryFormatter ); CSObjectManager.RegisterCustomFormatter( csmEmployeeDetails, TCSEmployeeDetailsFormatter ); end.