Delphi雖然為我們提供極其強大的調試功能查找Bug仍然是一項艱巨的工作通常我們寫代碼和調試代碼的所消耗的時間是大致相同的甚至有可能更多為了減少無謂的時間和精力的浪費有時我們還是需要專業調試工具的幫助來提高鎖定Bug的效率本文中我們將介紹著名的調試工具CodeSite Pro (它獲得了年度Delphi Informant讀者選擇的最佳調試工具獎的第二名)它的官方網址是
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
()創建一個新的項目然後切換組件面板到CodeSite頁面(CodeSite安裝後會在系統中安裝兩個組件TCSGlobalObject和TCSObject)選擇TCSGlobalObject組件然後放到窗體上TCSGlobalObject組件提供了設計時對全局TCodeSite對象的交互(全局TCodeSite是在CSInft單元中被初始化的)
()添加一個按鈕然後在它的OnClick事件中寫下如下代碼
//CodeSite就是全局的TCodeSite對象
CodeSiteSendMsg(CodeSite的第一條消息);
()編譯並運行這個簡單的程序運行後點擊按鈕CodeSite Dispatcher和CodeSite Viewer將會運行同時在CodeSite Viewer的消息列表中將會看到程序發出的消息(注意我們沒有必要在程序運行前啟動CodeSite Dispatcher和CodeSite Viewer因為TCodeSite 對象在需要發送消息的時候會自動啟動它們的)運行結果如下圖所示
圖
()接下來停止程序在OnClick事件處理過程中添加下面代碼
CodeSiteSendObject(Form Form )
()重新編譯運行程序再點按鈕一次這回你會在CodeSite Viewer中看到兩條消息其中Form對應的消息包括Form的對象信息
()為了看到Form的相關聯的對象信息選擇CodeSite Viewer的菜單命令View|Inspector會在消息列表右側顯示一個新的面板Form的published屬性都被顯示在其中如下圖所示
圖
()再次停止程序然後修改OnClick過程中代碼如下
CodeSiteEnterMethod(ButtonClick);
CodeSiteSendMsg(CodeSite的第一條消息);
CodeSiteSendObject(Form Form );
CodeSiteExitMethod(ButtonClick );
()這次我們再運行程序點擊按鈕後就會看到CodeSite的第一條消息和Form的消息被縮進在ButtonClick消息之間如下圖所示
圖
通過添加EnterMethod和ExitMethod方法的調用我們可以生成一個日志來記錄方法何時被調用
看過例子之後我們就會發現CodeSite的功能是非常強大的我們只要簡單的在程序中添加幾條語句就可以生成非常詳細的信息並通過CodeSite Viewer以生動的圖表表現出來接下來我們再來談談CodeSite的高級應用技術
發送消息到日志文件
每個程序或多或少都會有Bug不在這時發生也會在那時發生短時間內不發生很長時間就可能發作有時反復出現有時非常偶然的才能被發現如果一個人告訴你他寫的程序在任何時候都沒有任何問題他一定是在撒謊正是由於Bug的偶然性和隱蔽性就使得我們往往很難重復用戶提交的Bug這就給我們調試程序並找到問題的原因產生了極大的障礙而CodeSite能夠發送消息到日志文件的特性就使得用戶報告Bug變得更容易他們只要把運行時生成的信息文件提交就可以了相應的我們調試程序的工作也會變得更輕松我們可以使用CodeSite Viewer來直觀的分析錯誤發生的原因和位置
要想改變消息發送的目標我們可以通過設定TCodeSite 對象的DestinationDetails屬性來實現這項功能要求客戶的機器上必須安裝了CodeSite Dispatcher它屬於CodeSite中可自由分發的部分下面的要講具體過程仍然是基於前面講過的例子
()在窗體的OnCreate事件中添加下面代碼
CodeSiteDestinationDetails = File[Path=CFirstLogcsl]
()編譯並余興程序這回我們在點擊按鈕後消息就不再被發送給CodeSite Viewer而是發送到C盤的FirstLogcsl文件中
()使用CodeSite Viewer加載FirstLogcsl文件這回我們就象先前一樣察看被保存的CodeSite消息了
()如果我們想把消息同時發送到CodeSite Viewer和日志文件的話只修改前面的代碼為
CodeSiteDestinationDetails = ViewerFile[Path=CFirstLogcsl]
發送用戶定制的數據
雖然TCodeSite 類提供了大量的處理不同數據類型的方法但有時我們可能會需要發送某種自定義格式的數據信息為此TCodeSite 類定義了SendCustomData 方法它支持發送任意的數據類型並會根據一個自定義的格式器來格式化數據以便CodeSite Viewer可以正確的顯示數據
首先我們需要創建一個TCSFormatter 對象的子類然後重載對象的FormatDataInspectorType和TypeName方法然後調用CodeSite對象管理器對象CSObjectManager的來注冊新的TCSFormatter子類此外我們還需要調用RegisterCustomFormat方法來注冊一個新的消息類型
下面是一個實際應用的例子單元CSEmployeepas中實現了一個TCSEmployeeRecord記錄類型的定制格式器
unit CSEmployee;
interface
uses
Windows Graphics CSIntf;
const
csmEmployeeSummary = csmUser + ;
csmEmployeeDetails = csmUser + ;
首先在Uses部分添加對CSIntf 單元的引用第二步是為每一個格式器定義新的CodeSite消息類型常數上面我們定義了兩個常數注意常數應該大於csmUser但不能大過
type
TCSEmployee = record
LastName: string;
FirstName: string;
Address: string;
City: string;
State: string;
ZipCode: string;
PhoneNumber: string;
HireDate: TDateTime;
Salary: Currency;
VacationDays: Integer;
SickDays: Integer;
Manager: Boolean;
end;
上面的記錄就是我們要發送的自定義的數據類型
TCSEmployeeSummaryFormatter = class( TCSFormatter )
public
function InspectorType: TCSInspectorType; override;
procedure FormatData( var Data ); override;
function TypeName: string; override;
end;
TCSEmployeeDetailsFormatter = class( TCSFormatter )
public
function InspectorType: TCSInspectorType; override;
procedure FormatData( var Data ); override;
function TypeName: string; override;
end;
上面是兩個定制的格式器類的定義第一個格式器將把TCSEmployee 記錄格式化為一個文本格式第二個格式化器將把TCSEmployee 記錄格式化為網格樣式
實現一個自定義的格式化器的第一步是確定哪種類型的內置察看器將被用來察看格式化後的數據這裡使用的是字符串列表察看器察看器類型將被FormatData方法所使用
procedure TCSEmployeeSummaryFormatterFormatData( var Data );
var
EmpRec: TCSEmployee;
begin
EmpRec := TCSEmployee( Data );
AddLine( EmpRecFirstName + + EmpRecLastName );
AddLine( EmpRecAddress );
AddLine( EmpRecCity + + EmpRecState + + EmpRecZipCode );
AddLine( );
AddLine( Phone: + EmpRecPhoneNumber );
AddLine( Hire Date: + DateToStr( EmpRecHireDate ) );
AddLine( Salary: + Format( %m [ EmpRecSalary ] ) );
AddLine( );
AddLine( Vacation Days: + IntToStr( EmpRecVacationDays ) );
AddLine( Sick Days: + IntToStr( EmpRecSickDays ) );
if EmpRecManager then
AddLine( Manager: Yes )
else
AddLine( Manager: No );
end;
FormatData 方法是核心部分注意傳遞給FormatData方法的Data參數是一個無類型的可變參數這就意味著這個參數可以是任何數據類型的通過格式注冊過程我們可以確保強制類型映射為自定義的數據記錄而不會發生轉換錯誤
轉換數據類型後我們就可以對數據進行格式化了這裡使用TCSFormatter 基類的 AddLine方法在字符串間添加分割線來進行格式化
function TCSEmployeeSummaryFormatterTypeName: string;
begin
Result := TCSEmployee;
end;
TypeName方法的重載是可任選的但通常我們可以用它來返回顯示在消息列表中的字符串
{=========================================}
{== TCSEmployeeDetailsFormatter Methods ==}
{=========================================}
function TCSEmployeeDetailsFormatterInspectorType: TCSInspectorType;
begin
Result := itStockGrid;
end;
對於employeedetails格式器來說命名網格察看器將被用來察看數據信息
procedure TCSEmployeeDetailsFormatterFormatData( var Data );
var
EmpRec: TCSEmployee;
begin
EmpRec := TCSEmployee( Data );
AddNameValuePair( LastName EmpRecLastName );
AddNameValuePair( FirstName EmpRecFirstName );
AddNameValuePair( Address EmpRecAddress );
AddNameValuePair( City EmpRecCity );
AddNameValuePair( State EmpRecState );
AddNameValuePair( ZipCode EmpRecZipCode );
AddNameValuePair( PhoneNumber EmpRecPhoneNumber );
AddNameValuePair( HireDate EmpRecHireDate );
AddNameValuePair( Salary Format( %m [ EmpRecSalary ] ) );
AddNameValuePair( VacationDays EmpRecVacationDays );
AddNameValuePair( SickDays EmpRecSickDays );
AddNameValuePair( Manager EmpRecManager );
end;
這裡為了在網格察看器中格式化數據我們使用AddNameValuePair方法來實現
function TCSEmployeeDetailsFormatterTypeName: string;
begin
Result := TCSEmployee;
end;
下面兩個過程是用來封裝對SendCustomData方法的調用的這裡對全局的TCodeSite對象實例CodeSite進行了調用
{=====================}
{== Support Methods ==}
{=====================}
procedure CSSendEmployeeSummary( const Msg: string; EmpRec: TCSEmployee );
begin
CodeSiteSendCustomData( csmEmployeeSummary Msg EmpRec );
end;
procedure CSSendEmployeeDetails( const Msg: string; EmpRec: TCSEmployee );
begin
CodeSiteSendCustomData( csmEmployeeDetails Msg EmpRec );
end;
最後不要忘了調用CSObjectManagerRegisterCustomFormatter方法把格式器注冊到CodeSite對象管理器中
initialization
CSObjectManagerRegisterCustomFormatter( csmEmployeeSummary
TCSEmployeeSummaryFormatter );
CSObjectManagerRegisterCustomFormatter( csmEmployeeDetails
TCSEmployeeDetailsFormatter );
end
除了上面談到的特性外CodeSite還支持遠程調試也就是可以把消息路由到一台遠程機器上的日志文件或Code Viewer上由於通常時候我們很少會用到這一特性這裡就不進行詳細的討論了
From:http://tw.wingwit.com/Article/program/Delphi/201311/24692.html