親 手 打 造 C++Builder 的 TRACE Window
蕭 永 哲 martins1@ms3.hinet.net
曾 經 用 過 Visual C++ 1.5 的 讀 者 們 都 知 道 , 你 可 以 使 用 TRACE 這 個 巨 集 將 需 要 Debug 的 訊 息 送 到 一 個 名 為 DBWIN 的 視窗 上 , 而 咱 們 可 以 一 邊 執 行 程 式 , 一 邊 觀 看 DBWIN 中 由 程式 所 輸 出 過 來 的 TRACE 訊 息 。 但 不 幸 的 事 ,Visual C++ 1.5 的下 一 個 版 本Visual C++ 4.X 竟 然 開 始 不 包 含 了DBWIN , 為 什 麼 呢? 原 來 是 原 先DBWIN 裡 頭 所 包 含 的 所 有 功 能 都 被 整 合 到 了Visual C++ 的 整 合 環 境 裡 頭 , 且 功 能 更 被 發 揚 光 大 , 但TRACE 巨 集從 現 在 起 只 存 在 於 除 錯 模 式 , 並 且 還 被 硬 性 規 定 只 許 應用 程 式 在 整 合 環 境 內 執 行 時 其TRACE 功 能 才 能 夠 使 用 。 這對 於 只 不 過 想 要 像 使 用 printf 一 樣 簡 單 的 輸 出 一 點 訊 息 的咱 們 , 卻 必 須 動 用 到 這 樣 龐 大 的 除 錯 器 似 乎 有 點!@#$%^ 。
幸 好 在 Microsoft System Journal (MSJ) 上 Paul DiLascia 生 生 的 兩篇 文 章 為 我 們 打 造 了 一 個 叫 簡 單DBWIN 稱 為TraceWin , 稍 微 補足 了 這 項 小 小 的 不 快 。 對 於 使 用 Visual C++ 4.x 的 使 用 者 來說 , 有 如 久 旱 逢 甘 霖 一 般 。 在 當 時 筆 者 也 跟 著 依 樣 畫 葫蘆 地 自 己 打 造 了 一 個TraceWin ; 但 自 從1997 的 白 色 情 人 節 過後 筆 者 與C++Builder 結 下 了 不 解 之 緣 , 用C++Builder 開 發 了 許多 專 案 , 但 總 覺 得 缺 少 了 一 點 什 麼 。 對 ! 就 是C++Builder 專用 的TraceWin 。C++Builder 的 除 錯 環 境 很 難 使 用 嗎 ? 一 點 也 不! 功 能 強 大 的 令 人 瞠 目 結 舌 ; 但 我 只 是 希 望 能 夠 簡 單 的檢 查 程 式 的 流 程 、 資 料 的 內 容 , 並 不 需 要 動 用 到 這 麼 龐大 的 除 錯 環 境 。 我 要 的 只 是 希 望 能 把Debug 的 動 作 弄 得 簡單 點 。
以 往 在DOS 的 文 字 模 式 下 ,printf 是 個 簡 單 又 好 用 的 除 錯幫 手 , 可 以 在 需 要 檢 查 的 位 置 適 時 地 加 入printf 將 除 錯 資訊 輸 出 到 螢 幕 上 。 而 在Windows Programming 中 ,printf 或 許 還 能夠 在Consol Mode 裡 佔 據 從 前 保 有 的 江 山 , 但 在 圖 形 界 面 裡printf 已 毫 無 用 武 之 地 , 簡 單 的Debug 動 作 可 以 由 訊 息 對 話 盒 (Message Box ) 來 代 替 龐 大 的 除 錯 環 境 , 但 使 用Message Box 會 打 斷 原有 程 式 的 執 行 並 且 將 原 始 視 窗 被 遮 蓋 住 , 最 討 厭 的 是 還得 去 按 個OK 才 能 讓 程 式 繼 續 執 行 , 並 且 在 按 下OK 後 原 始 視窗 還 收 到 一 個 不 速 之 客 : 不 必 要 的WM_PAINT 訊 息 , 這 讓 我們 的 程 式 多 做 了 一 個 重 繪 的 動 作 , 訊 息 對 話 盒 似 乎 是 個很 不 好 的 選 擇 , 但 在 沒 有 TraceWin 之 前 , 這 是 唯 一 也 較 簡單 的 選 擇 了 , 有 多 簡 單 呢 ? 在C++Builder 下 只 要 使 用 ShowMessage() 這 個 函 示 , 就 可 產 生 一 個 文 字 訊 息 視 窗 。
- 不 同 行 程 間 的 資 料 傳 送
WM_COPYDATA 訊 息 的 使 用 如 下 ( 發 出 訊 息 端 ) :
SendMessage(
hwndReceiver,
WM_COPYDATA,
(WPARAM)hwndSender,
(LPARAM)&cds
);
hwndReceiver : 接 收 訊 息 端 的 視 窗 代 碼
hwndSender : 發 出 訊 息 端 的 視 窗 代 碼
cds : 需 要 傳 送 的 資 料 的 指 標
其 中LPARAM 參 數 中 的 cds 指 向 一 個 特 定 的Windows 資 料 結 構:COPYDATASTRUCT , 其 結 構 如 下 :
typedef struct tagCOPYDATASTRUCT { // cds DWORD dwData;
DWORD cbData;
PVOID lpData;
}
COPYDATASTRUCT,*PCOPYDATASTRUCT; dwData : 為 使 用 者 的 自 定 值, 使 用 者 可 以 透 過 此 值 來 通 知 接 收 端 其 傳 送 的 資 料 類 別以 及 傳 送 的 資 料 用 途 。
cbData : lpData 所 指 到 的 資 料 大 小 , 以 byte 為 單 位 。
LpData : 所 要 傳 送 資 料 的 起 始 指 標 。 資 料 的 內 容 可 以 是任 何 的 資 料 。
- 取 得 接 收 端 的 視 窗 代 碼
1. 使 用FindWindow
FindWindow 是 Windows API ,FindWindow 的 原 始 定 義 為 :
HWND FindWindow(
LPCTSTR lpClassName, // pointer to class name
LPCTSTR lpWindowName // pointer to window name
);
lpClassName : 為 視 窗 在 建 立 時 所 註 冊 的 視 窗 類 別
lpWindowName : 為 視 窗 的 名 稱 , 通 常 是 視 窗 的 標 題( Caption )
由 這 個 API 的 定 義 就 不 難 看 出 , 只 要 我 們 知 道 接 收 端 的視 窗 標 題 或 是 知 道 接 收 端 註 冊 的 視 窗 類 別 , 我 們 就 有 辦法 把 訊 息 給 傳 送 出 去 。 但 若 有 相 同 的 兩 個 視 窗 先 後 被 執行 時 怎 麼 辦 呢 ? FindWindow 會 找 到 先 執 行 的 那 個 視 窗 , 並取 回 其 視 窗 代 碼 。
所 以 , 只 要 接 收 端 的 視 窗 標 題 不 會 改 變 , 那 麼 我 們 就可 以 使 用FindWindow 來 取 得 接 收 端 的 視 窗 代 碼 。
2. 註 冊 視 窗 訊 息 與 廣 播 訊 息 (Register Window Message and Broadcast Message )
使 用 『 註 冊 視 窗 訊 息 與 廣 播 訊 息 』 是 在 於 你 不 想 用FindWindow 的 狀 況 下 使 用 的 。 為 什 麼 會 不 想 要 用FindWindow 呢 ? 就 是 先前 才 提 到 的 : 接 收 端 的 視 窗 標 題 會 改 變 , 若 你 寫 的 程 式須 常 常 變 換 視 窗 標 題 , 那 麼 在 使 用FindWindow 時 不 就 很 難 處理 嗎 ? 還 必 須 知 道 接 收 端 的 視 窗 標 題 改 變 成 什 麼 。
因 此 可 換 成 使 用 註 冊 視 窗 訊 息 與 廣 播 訊 息 的 方 式 來 取得 接 收 端 的 視 窗 代 碼 : 在 程 式 第 一 次 執 行 時 , 發 出 端 透過SendMessage 將 一 個 已 經 註 冊 好 的 視 窗 訊 息 給 廣 播 出 去2 ,當 接 收 端 收 到 這 個 已 經 註 冊 過 的 特 定 訊 息 時 , 會 回 傳 另一 個 自 訂 的 訊 息 給 廣 播 訊 息 的 發 出 端 , 而 且 這 個 回 傳 的訊 息 裡 還 帶 有 接 收 端 的 視 窗 代 碼 , 如 此 一 來 就 發 出 端 就可 以 掌 握 住 接 收 端 的 視 窗 代 碼 , 這 樣 一 來 , 即 使 接 收 端的 視 窗 標 題 常 常 在 變 換 , 對 發 出 端 來 說 要 將 訊 息 傳 送 給指 定 的 接 收 端 也 不 是 什 麼 難 事 了 。 但 使 用 這 種 方 法 的 前提 是 在 於 在 發 出 訊 息 端 程 式 執 行 前 接 收 訊 息 端 就 必 須 得先 執 行 了 , 不 然 沒 有 辦 法 傳 回 接 收 端 的 視 窗 代 碼 。 當 然了 , 若 你 要 每 次 發 送 訊 息 前 來 做 視 窗 代 碼 的 傳 遞 也 無 不可 , 只 是 這 樣 一 來 一 往 會 浪 費 掉 許 多 訊 息 傳 送 的 時 間 。
上 述 這 兩 個 方 法 都 很 不 錯 , 但 使 用 註 冊 視 窗 訊 息 與 廣播 訊 息 一 來 一 往 傳 遞 訊 息 次 數 過 於 頻 繁 , 且 所 撰 寫 需 程式 碼 也 較 為 多 些 , 筆 者 暫 時 不 考 慮 使 用 。
- 接 收 端 接 收 訊 息
- C++Builder 應 用 程 式 啟 動 流 程
先 看 看 是 否 真 的 如 筆 者 所 說TForm1 真 的 是 由TApplication 所掌 管 呢 ? 可 惜C++Builder 所 附 上 的WinSight 並 沒 有 辦 法 查 出TForm1 的 管 理 者 也 就 是 擁 有 者 是 那 個 視 窗 , 所 以 我 們 無 法 由WinSight 看 出 任 何 的 端 倪 , 不 過 幸 好 使 用 筆 者 使 用Visual C++ 所 附 上與WinSight 功 能 相 似 的Spy++ 倒 是 可 以 讓 我 們 瞧 見 所 要 的 證 據( 圖 三 ) :
由 圖 三 很 清 楚 的 可 以 看 出 ,TForm1 的Owner Window 就 是 隱 藏起 來 的TApplication 。 由 以 上 所 蒐 集 的 資 料 來 看TApplication 類別 , 似 乎TApplication 類 別 是 由C++Builder 所 建 立 的 應 用 程 式 中的 大 總 管 , 這 一 切 也 在Project1 的Project1.cpp 裡 透 露 出 這 祕密 :
程 式 列 表 一: 、Project1.cpp :
- #include
- Application->Initialize(); 進 行Application 物 件 的 初 始 化 , 如 COM 與 OLE Automation 的 初始 化 Application->CreateForm(__classid(TForm1), &Form1);
開 始 建 立 主 視 窗 , 也 就 是Form1 , 並 顯 示Form1 Application->Run();
喔 ! 原 來 整 個 由C++Builder 所 建 立 的 應 用 程 式 所 收 到 的 視窗 訊 息 , 都 會 由Application 這 全 域 物 件 來 接 收 , 並 且 透 過訊 息 在VCL 架 構 裡 的 流 動 把 訊 息 分 配 到 該 分 配 的 地 方 去 ,但 僅 止 於 他 認 得 的 訊 息 。 為 什 麼 說 僅 止 於 他 認 得 的 呢 ?這 得 討 論 到VCL 的 內 部 架 構 , 且 聽 筆 者 慢 慢 說 下 去 。
- VCL 對 訊 息 的 處 理
1 、 覆 載Dispatch 虛 擬 函 式 : ( 使 用Message Map )
在53 期 的 『BCB 與Win32 訊 息 傳 遞 』 一 文 內 曾 用 到 以 下 的 做法 來 接 收 並 處 理 訊 息 :
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER( … …)
END_MESSAGE_MAP( …)
但 這 個 機 制 到 底 是 怎 麼 做 成 的 呢 ? 咱 們 把 他 從sysdef.h 裡 給 挖 出 來 瞧 一 瞧 :
程 式 列 表 二 、sysdefs.h :
- #define BEGIN_MESSAGE_MAP virtual void __fastcall Dispatch(void *Message) \ { \ switch (((PMessage)Message)->Msg) \ { #define VCL_MESSAGE_HANDLER(msg,type,meth) \ case msg: \ meth(*((type *)Message)); \ break; // NOTE: ATL defines a MESSAGE_HANDLER macro which conflicts with VCL's macro. The // VCL macro has been renamed to VCL_MESSAGE_HANDLER. If you are not using ATL, // MESSAGE_HANDLER is defined as in previous versions of BCB. // #if !defined(USING_ATL) && !defined(USING_ATLVCL) && !defined(INC_ATL_HEADERS) #define MESSAGE_HANDLER VCL_MESSAGE_HANDLER #endif // ATL_COMPAT #define END_MESSAGE_MAP(base) default: \ base::Dispatch(Message); \ break; \ } \ }
這 個 巨 集 在 類 別 的 定 義 中 以 虛 擬 的 方 式 重 新 覆 載 了Dispatch 函 式 , 而Dispatch 的 原 始 定 義 到 底 是 什 麼 呢 ?Dispatch 原 以 虛擬 的 方 式 定 義 在TObject 裡 , 而 其 目 地 是 在 於 視 窗 訊 息 的 分配 。 而 緊 接 著 在 這 個 虛 擬 函 示 宣 告 之 後 的 是 一 個switch 敘述 , 用 來 分 派 視 窗 訊 息 交 給 之 後 的 VCL_MESSAGE_HANDLER 巨 集來 處 理 。
B 、VCL_MESSAGE_HANDLER : (606 行 至617 行 )
先 看 到615 至617 這 幾 行 程 式 , 你 可 以 發 現 , 若 沒 有 使 用ATL (Active Template Libray ) ,MESSAGE_HANDLER 就 自 動 被 定 義 成VCL_MESSAGE_HANDLER , 但 筆 者 在 這 裡 還 是 建 議 你 , 多 打 幾 個 字 會 比 較 保 險 些, 以 免 以 後 在 移 植 程 式 時 因 為 這 小 小 的 疏 忽 而 造 成 程 式的 錯 誤 , 而 一 時 無 法 察 覺 。
再 看 到607 至609 行 , 可 以 發 現 是 直 接 在BEGIN_MESSAGE_MAP 的switch 敘 述 之 後 加 入case 進 入 點 來 處 理 指 定 視 窗 訊 息 的 。 舉 個 例子 來 說 , 若 我 們 使 用VCL_MESSAGE_HANDLER 這 個 巨 集 如 下 :
VCL_MESSAGE_HANDLER(WM_COPYDATA,TMessage,OnCopyData)
則 會 會 被 展 開 成 :
case WM_COPYDATA:
OnCopyData(*((TMessage *) Message));
break;
C 、END_MESSAGE_MAP : (619 行 至623 行 )
想 當 然 耳 ,END_MESSAGE_MAP 巨 集 的 用 途 應 該 就 是 來 終 結switch 敘 述 , 但 你 可 以 發 現620 行 上 頭 的base::Dispatch(Message) 是 用來 將 不 認 得 的 訊 息 交 給 父 類 別 的Dispatch 虛 擬 函 式 做 處 理。
因 此 所 以 若 我 們 加 入 以 下 的 範 例 : BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(WM_COPYDATA,TMessage,OnCopyData)
END_MESSAGE_MAP(TForm);
則 會 被 編 譯 成 : virtual void __fastcall Dispatch(void *Message)
{
switch (((PMessage)Message)->Msg)
{
case WM_COPYDATA:
OnCopyData(*((TMessage *) Message)); // 處 理WM_COPYDATA 的 函 式
break;
default:
TForm::Dispatch(Message);
break;
}
}
Dispatch 的 原 始 定 義 是 在 於TObject 這 個 基 礎 類 別 上 , 只要 是TObject 的 衍 生 類 別 都 可 以 覆 載 這 個 虛 擬 函 式 來 處 理 自己 特 定 的 訊 息 , 但 若Dispatch 不 認 得 這 個 送 來 的 訊 息 最 後會 把 處 理 程 序 丟 改TObject::DefaultHandler 這 個 函 式 處 理 , 不過TObject::DefaultHandler 並 未 實 作 任 何 的 動 作 , 真 正 的 功 能還 必 須 透 過 以 後 的 覆 載 來 達 成 。 挖VCL 的 原 始 碼 很 有 趣 吧! 若 還 覺 得 光 聽 筆 者 這 樣 說 說 還 不 夠 有 趣 的 話 , 可 以 自行 挖 掘 這 段VCL 原 始 碼 ; 在 此 要 先 說 明 一 下 , 若 讀 者 們 購買 的 是C++Builder 專 業 版 或 更 高 級 的 版 本 , 光 碟 片 裡 頭 都 會含 有VCL 的 原 始 碼 , 但 若 是 標 準 版 就 沒 有 附 上 原 始 始 碼 了。 有 志 挖 掘VCL 架 構 的 內 部 奧 祕 者 都 可 以 自 己 來 探 求VCL 背後 的 真 面 目 。OK !TObject::Dispatch 的 原 始 碼 你 可 以 在 :source\vcl\system.pas 裡 頭 找 到 , 有 興 趣 的 讀 者 可 以 把 這 段 程 式 給 拿 出 來 看 ,不 過 先 透 露 一 下 , 為 了 加 速 訊 息 的 傳 遞 ,Borland 的VCL 開 發小 組 把TObject::Dispatch 函 式 都 採 用 組 合 語 言 來 撰 寫 , 已 達加 速 處 理 的 目 的 。
但 僅 只 有 覆 載Dispatch 這 一 種 方 法 嗎 ? 不 還 有 很 多 呢 !接 著 看 下 去 。
2 、 覆 載WndProc 函 式
剛 才 談 過TObject::Dispatch 的 做 法 , 再 來 談 談TControl 裡 頭 對於 訊 息 的 處 理 , 在TControl 裡 頭 處 理 訊 息 有 兩 個 一 個 是 由TObject 衍 生 而 來 的Dispatch , 但 在TControl 裡 頭 並 沒 有 複 寫Dispatch 函式 , 依 舊 採 用TObject 裡 頭 的Dispatch 函 式 , 而 另 一 個 方 式 就是WndProc 函 式 , 先 來 瞧 瞧TControl 類 別 裡 頭 的WndProc 做 些 什 麼:
程 式 列 表 三 、controls.pas :
- procedure TControl.WndProc(var Message: TMessage); var Form: TCustomForm; 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; 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 BeginDrag(True); Exit; end; Include(FControlState, csLButtonDown); end; WM_LBUTTONUP: Exclude(FControlState, csLButtonDown); end; end; Dispatch(Message); end;
- procedure TWinControl.WndProc(var Message: TMessage); var Form: TCustomForm; begin case Message.Msg of WM_SETFOCUS: begin Form := GetParentForm(Self); if (Form <> nil) and not Form.SetFocusedControl(Self) then Exit; end; WM_KILLFOCUS: if csFocusing in ControlState then Exit; WM_NCHITTEST: begin inherited WndProc(Message); if (Message.Result = HTTRANSPARENT) and (ControlAtPos(ScreenToClient(SmallPointToPoint( TWMNCHitTest(Message).Pos)), False) <> nil) then Message.Result := HTCLIENT; Exit; end; WM_MOUSEFIRST..WM_MOUSELAST: if IsControlMouseMsg(TWMMouse(Message)) then Exit; WM_KEYFIRST..WM_KEYLAST: if Dragging then Exit; WM_CANCELMODE: if (GetCapture = Handle) and (CaptureControl <> nil) and (CaptureControl.Parent = Self) then CaptureControl.Perform(WM_CANCELMODE, 0, 0); end; inherited WndProc(Message); end;
- function TWinControl.IsControlMouseMsg(var Message: TWMMouse): Boolean; var Control: TControl; P: TPoint; begin if GetCapture = Handle then begin Control := nil; if (CaptureControl <> nil) and (CaptureControl.Parent = Self) then Control := CaptureControl; end else Control := ControlAtPos(SmallPointToPoint(Message.Pos), False); Result := False; if Control <> nil then begin P.X := Message.XPos - Control.Left; P.Y := Message.YPos - Control.Top; Control.Perform(Message.Msg,Message.Keys,Longint( PointToSmallPoint(P))); Result := True; end; end;
由 以 上 可 知 , 若 要 在 接 收 端 接 收 訊 息 , 除 了 覆 載Dispatch 函 式 外 , 也 可 以 在 接 收 端 重 新 覆 載WndProc 這 個 函 式 來 達 到訊 息 的 處 理 目 的 。 方 法 如 下 :
- void __fastcall TForm1::WndProc(TMessage &Message) { switch(Message.Msg) { case WM_COPYDATA: OnCopyData(Message);// 處 理WM_COPYDATA 的 函 式 break; } TForm::WndProc(Message); }
先 前 說 過 ,Application 物 件 是 整 個 應 用 程 式 的 幕 後 黑 手, 由 程 式 列 表 一 的13 行 開 始 進 入 整 個 應 用 程 式 的 訊 息 迴圈 , 一 直 到 收 到WM_QUIT 這 個 訊 息 才 跳 出 迴 圈 並 結 束 程 式 的執 行 。
先 來 看 看TApplication::Run 到 底 是 怎 麼 讓 程 式 進 入 訊 息 迴圈 :
程 式 列 表 四 、forms.pas
- procedure TApplication.Run; begin FRunning := True; try AddExitProc(DoneApplication); if FMainForm <> nil then begin case CmdShow of SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized; SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized; end; if FShowMainForm then if FMainForm.FWindowState = wsMinimized then Minimize else FMainForm.Visible := True; repeat HandleMessage until Terminated; end; finally FRunning := False; end; end;
- procedure TApplication.HandleMessage; begin if not ProcessMessage then Idle; end;
- function TApplication.ProcessMessage: Boolean; var Handled: Boolean; Msg: TMsg; begin Result := False; if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then begin Result := True; if Msg.Message <> WM_QUIT then begin Handled := False; if Assigned(FOnMessage) then FOnMessage(Msg, Handled); if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then begin TranslateMessage(Msg); DispatchMessage(Msg); end; end else FTerminate := True; end; end;
void __fastcall ApplicationOnMessage(tagMSG &Msg, bool &Handled)
若 你 不 希 望VCL 繼 續 處 理 這 個 訊 息 的 話 , 只 需 把Handled 這個 旗 標 給 設 定 成true 即 可 。 就 讓 筆 者 舉 個 簡 單 例 子 :
- void __fastcall TForm1::FormCreate(TObject *Sender) { Application->OnMessage = ApplicationOnMessage; } //-------------------------------------------------------------- void __fastcall TForm1::ApplicationOnMessage(tagMSG &Msg, bool &Handled) { switch(Msg.message) { case WM_COPYDATA: TMessage Message; Message.Msg = Msg.message; Message.WParam = Msg.wParam; Message.LParam = Msg.lParam; OnCopyData(Message);// 處 理WM_COPYDATA 的 函 式 Handled = true; break; } } //--------------------------------------------------------------
來 取 得Application->Handle 。
B 、 用FindWindow("TForm1","Form1");
來 取 得Form1->Handle 。
- VCL 視 窗 訊 息 的 流 動 方 向
根 據 這 張 訊 息 流 通 圖 來 看 , 這 三 種 方 法 所 接 收 到 的 訊息 先 後 為 :
- TApplication::OnMessage 覆 載 的WinProc 覆 載 的Dispatch
- 親 手 打 造TraceWin
先 來 設 計 一 個 簡 單 板 的 訊 息 發 出 端 , 放 置 兩 個Button 元件 以 及 一 個Edit 元 件 到Form1 上 面 , 其 擺 設 如 圖 六 。
為Clear 按 鈕 加 上OnClick 事 件 , 用 來 清 除Edit1 元 件 上 的 文字 :
- void __fastcall TForm1::ClearBtnClick(TObject *Sender) { Edit1->Text = “ ”;// 清 除Edit1->Text 的 內 容 }
- void __fastcall TForm1::SendBtnClick(TObject *Sender) { char Msg[255]; sprintf(Msg,"%s",Edit1->Text.c_str()); COPYDATASTRUCT *pcp = new COPYDATASTRUCT; pcp->dwData = 0; pcp->cbData = sizeof(Msg); pcp->lpData = &Msg; SendMessage(FindWindow("TForm1","TRACE Window"), WM_COPYDATA, (WPARAM)NULL, (LPARAM)pcp); delete pcp; }
- SendMessage(FindWindow("TApplication","TRACE Window"), WM_COPYDATA, (WPARAM)NULL, (LPARAM)pcp);
OK! 剛 剛 已 經 把 簡 單 版 本 的 訊 息 發 出 端 給 設 置 好 , 接 下來 就 是 要 把 接 收 端 給 搞 定 了 。 先 做 一 個 簡 單 的 接 收 端 來接 收 由 發 出 端 所 發 出 的 文 字 訊 息 並 秀 出 來 , 所 以 在Form1 上 放 置 一 個Memo ( 如 圖 七 ) , 且 設 定Memo 的Align 屬 性 為alClient , 並 把 其 中Lines 的 內 容 給 刪 除 。 但 是 要 記 住 , 我 們 先 得把Form1 的Caption 給 設 定 成 『TRACE Window 』 讓 發 出 端 能 夠 正 確使 用FindWindow 來 取 得 接 收 端 的 視 窗 代 碼 。 另 外 為 了 順 便 是一 下 這 三 種 接 收 訊 息 的 方 法 , 也 要 記 得 把 到Project Option 裡 把Application 的Title 給 改 成 『TRACE Window 』 ( 如 圖 八 ) , 或是 在Project1.cpp 裡 頭 加 上 指 定Application->Title 的 程 式 碼 , 如下 :
- Application->Initialize(); Application->Title = “TRACE Window ”; Application->CreateForm(__classid(TForm1), &Form1);
private: // User declarations
void __fastcall OnCopyData(TMessage &Msg);
在Unit1.cpp 裡 加 入 :
- void __fastcall TForm1::OnCopyData(TMessage &Msg) { PCOPYDATASTRUCT pcp; pcp = (PCOPYDATASTRUCT)Msg.LParam; char* TraceMsg; TraceMsg = (char*)pcp->lpData; Memo1->Lines->Add(TraceMsg);// 將 接 收 到 的 陣 列 一 行 一 行加 到 給 到Memo1 裡 頭 }
在unit1.h 裡 加 入 :
- public: // User declarations BEGIN_MESSAGE_MAP VCL_MESSAGE_HANDLER(WM_COPYDATA,TMessage,OnCopyData) END_MESSAGE_MAP(TForm)
在unit1.h 裡 加 入 :
- protected: // User declarations void __fastcall WndProc(TMessage &Message);
- void __fastcall TForm1::WndProc(TMessage &Message) { switch(Message.Msg) { case WM_COPYDATA: OnCopyData(Message);// 處 理WM_COPYDATA 的 函 式 break; } TForm::WndProc(Message); }
在unit1.h 裡 加 入 :
- protected: // User declarations void __fastcall ApplicationOnMessage(tagMSG &Msg, bool &Handled);
- void __fastcall TForm1::FormCreate(TObject *Sender) { Application->OnMessage = ApplicationOnMessage; } //-------------------------------------------------------------- void __fastcall TForm1::ApplicationOnMessage(tagMSG &Msg, bool &Handled) { switch(Msg.message) { case WM_COPYDATA:
- TMessage Message; Message.Msg = Msg.message; Message.WParam = Msg.wParam; Message.LParam = Msg.lParam;
- OnCopyData(Message);// 處 理WM_COPYDATA 的 函 式 Handled = true; break; } } //--------------------------------------------------------------
3 、 訊 息 收 發 測 試 : OK ! 已 經 完 成 了 大 半 , 先 來 測 試一 下 吧 ( 圖 九 ) :
可 以 清 楚 的 看 到 , 我 們 可 已 經Edit1 元 件 裡 頭 的 文 字 完整 的 傳 送 到TRACE Window 裡 頭 去 , 但 這 樣 就 完 成 了 嗎 ? 那 可不 ! 還 有 很 多 地 方 應 該 加 強 或 修 正 的 :
- TRACE Window 的 視 窗 類 別 修 改
也 因 此 原 先 所 使 用 的FindWindow 要 改 成FindWindow( “TMainForm ”, ”TRACE Window ”) 。
B 、TRACE Window 的 功 能 加 強
嗯 , 改 好 了 視 窗 類 別 後 還 缺 什 麼 呢 ? 缺 的 可 多 了 ! 如把Memo1 元 件 上 的 文 字 給 存 檔 起 來 以 便 日 後 觀 看 、 清 除Memo1 元 件 上 的 內 容 或 是 增 加 進 制 轉 換 等 等 的 功 能 , 說 都 說 不完 。 這 些 都 是 應 該 且 必 須 的 , 該 怎 麼 做 呢 ? 這 些 功 能 筆者 在 此 僅 舉 幾 個 簡 單 的 說 明 , 不 再 詳 述 :
a 、Memo1 元 件 上 的 文 字 存 檔 : 在VCL 架 構 中 , 所 有 的VCL 元件 都 被 包 裝 的 非 常 完 整 , 所 以 做 起 例 行 工 作 來 非 常 的 容易 , 要 將Memo1 元 件 上 的 資 料 存 檔 只 需 如 下 :
Memo1->Lines->SaveToFile("D:\\trace.txt");
這 樣 就 會 把 資 料 存 到D:\trace.txt 裡 頭 了 。
不 過 這 樣 似 乎 有 點 太 過 簡 單 , 咱 們 再 加 上 一 個 存 檔 對話 盒 吧 :
- TSaveDialog *SD = new TSaveDialog(this); SD->Options <<> SD->Filter = "Text files (*.txt)|*.TXT"; if (SD->Execute()) { Memo1->Lines->SaveToFile(SD->FileName); } delete SD;
b 、 清 除Memo1 上 的 文 字 : 清 除Memo1 上 的 文 字 筆 存 檔 更 容 易 , 只 需 使 用Clear() 這 個成 員 函 式 , 如 下 : Memo1->Clear();
這 樣 就 可 以 輕 輕 鬆 鬆 的 將Memo1 元 件 上 頭 的 文 字 給 清 除了 。
C 、 訊 息 發 出 端 的 指 令 簡 化
根 據 先 前 的 訊 息 發 出 端 範 例 來 看 , 我 們 使 用 特 定 的 型別 才 能 夠 轉 換 , 可 用 性 頗 低 , 且 還 要 打 那 麼 多 的 字 , 若每 次 要 使 用 時 還 得 打 那 麼 多 字 , 豈 不 累 人 , 因 此 咱 們 可以 把 他 編 成 通 用 函 式 , 等 需 要 用 到 時 只 需include 我 們 所 編寫 的 函 式 檔 案 即 可 , 我 把 函 式 名 稱 命 名 為dout 有Debug Out 的意 思 , 並 且 讓dout 能 夠 支 援Unicode string 、signal byte string 以及VCL 裡 頭 特 有 的AnsiString , 如 此 一 來 這 個 表 頭 檔 在Visual C++ 、Borland C++ 以 及Borland C++Builder 等Win32 平 台 上 的C++ 編 譯 器 都一 樣 可 以 使 用 , 當 然 了 對 於 一 些 特 定 的 類 別 如AnsiString 會用#define 這 些 前 置 指 令 將 他 隔 離 , 讓 不 認 得 的 編 譯 器 不 會編 譯 到 這 些 個 不 認 得 的 類 別 。
程 式 列 表 五 、dout.h
- ////////////////////////////////////////////////////////////////////// // // Easy Bug Tracer v 1.0.6 // @(#) dout.h 1.0.6, last edit: 09/15/98 09:45 // @(#) Copyright (C) 1998 Martin Hsiao (martins1@ms3.hinet.net) // @(#) Martin's WorkShop (http://insidebcb.copystar.com.tw) // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // ////////////////////////////////////////////////////////////////////// #ifndef __DOUT_H_ #define __DOUT_H_ #include
程 式 列 表 六 、dout.pas
- ////////////////////////////////////////////////////////////////////// // // Easy Bug Tracer v 1.0.6 // @(#) dout.pas 1.0.6, last edit: 09/15/98 09:45 // @(#) Copyright (C) 1998 Martin Hsiao (martins1@ms3.hinet.net)
- // @(#) Martin's WorkShop (http://insidebcb.copystar.com.tw)
- // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // ////////////////////////////////////////////////////////////////////// unit douts; interface uses Windows, Messages; procedure dout(Data : pchar);overload; procedure dout(Data : String);overload; implementation procedure charout(Data : pchar);overload; var sSend : AnsiString; cdsData : TCopyDataStruct; begin sSend := Data; with cdsData do begin dwData := 0; cbData := Length(sSend) + 1; lpData := pchar(sSend); end; SendMessage(FindWindow('TMainFrame','TRACE Window'), WM_COPYDATA,WPARAM(nil),LPARAM(@cdsData)); end; procedure dout(Data : pchar);overload; begin charout(Data); end; procedure dout(Data : String);overload; begin charout(pchar(Data)); end; end.
- 結 語
Advanced Windows, Microsoft Press, Jeffrey Richter, Chapter 10.
Multithreading Application in Win32, Addison Wesley, Jim Beveridge & Robert Wiener, Chapter13.
Programming Windows 95, Microsoft Press, Charles Petzold, Chapert 17.
註 二 、 需 要 用 到 廣 播 的 訊 息 必 須 先 使 用RegisterWindowMessage 函 式 來 向 系 統 註 冊 。
沒有留言:
張貼留言