|
|
|||||||||||||||||||||||||
|
|
|
親 手 打 造 C++Builder 的 TRACE Window
蕭 永 哲 martins1@ms3.hinet.net
幸 好 在 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() 這 個 函 示 , 就 可 產 生 一 個 文 字 訊 息 視 窗 。
由 以 往 程 式 寫 作 經 驗 告 訴 我 們 : 要 將 較 大 量 的 資 料 在程 式 內 部 間 傳 遞 , 最 方 便 的 方 法 就 是 使 用 指 標 的 傳 遞 來達 成 ; 但 在Win32 平 台 上 不 同 行 程 間 的 定 址 空 間 各 自 獨 立的 , 因 此 要 藉 由 指 標 把 資 料 傳 送 給 另 外 一 個 行 程 在Win32 平 台 上 是 不 被 允 許 但 又 是 不 可 或 缺 的 , 想 當 然 耳Microsoft 當 然 會 注 意 到 這 個 問 題 , 因 此Microsoft 提 供 了 一 個WM_COPYDATA 這 個 視 窗 訊 息 來 替 個 各 行 程 間 透 過 指 標 來 將 傳 遞 資 料 ,使 用WM_COPYDATA 這 個 視 窗 訊 息 時 系 統 會 自 動 幫 我 們 把 指 標所 指 到 的 資 料 由 發 出 端 的 行 程 定 址 空 間 內 複 製 到 接 收 端的 行 程 定 址 空 間 裡 頭 , 並 將 複 製 後 的 資 料 指 標 通 知 給 接收 端 知 道 。 因 此 使 用 這 個 方 法 就 沒 有 必 要 去 考 慮 到 各 個行 程 間 的 定 址 空 間 的 問 題 。 此 外 使 用WM_COPYDATA 是 在 不 同執 行 緒 ( 不 論 這 兩 個 執 行 緒 是 否 為 同 一 行 程 內 ) 之 間 搬移 資 料 最 簡 單 的 一 種 做 法 了 , 不 過 要 接 收 這 個 訊 息 的 目的 地 必 須 擁 有 一 個 視 窗 代 碼 (Window Handle) , 也 就 是 必 須是 個 視 窗 , 為 什 麼 呢 ? 沒 為 什 麼 , 因 為 你 必 須 傳 遞 訊 息的 函 式SendMessage 的 目 的 地 必 須 是 個 視 窗 ,SendMessage 的 第 一個 參 數 就 是 接 收 訊 息 端 的 視 窗 代 碼 。 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; 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 下 , 處 理 視 窗 訊 息 的 方 式 有 很 多 種 , 我 們要 選 擇 哪 一 種 呢 ? 都 可 以 ! 你 只 要 確 定 你 能 夠 收 到 這 個訊 息 並 且 正 確 的 處 理 他 就 可 以 了 , 你 可 以 送 給 這 個 應 用程 式 的 主 控 制 視 窗 , 也 可 以 送 到 應 用 程 式 的 主 視 窗 。 主控 制 視 窗 ? 主 視 窗 ? 都 快 搞 糊 塗 了 , 這 到 底 怎 麼 說 呢 ?說 來 話 長 , 這 得 從C++Builder 所 開 發 出 來 的 應 用 程 式 的 啟 動流 程 談 起 。
再 利 用WinSight 先 來 看 看TApplication 的 更 深 入 的 內 容 ( 圖二 ) , 由 圖 中 可 以 看 到TApplication 的 視 窗 大 小 是 零 , 而 其 Windows text ( 視 窗 標 題 ) 是 Project1 , 其Class name ( 視 窗 類別 ) 是 TApplication ; 視 窗 大 小 是 『 零 』 , 也 就 是 說 這 個Window 是 看 不 見 的 ,TApplication 的 隱 形 頗 有 躲 在 背 後 的 意 義 。 看起 來 似 乎 這TApplication 似 乎 就 是 幕 後 的 黑 手 , 先 聲 明 只 是看 起 來 而 已 , 稍 後 會 想 辦 法 驗 證 的 。 而 這 裡 所 提 到 的 『視 窗 標 題 』 與 『 視 窗 類 別 』 其 實 就 是 先 前 在 提 到FindWindow 這 個API 所 使 用 的lpWindowName 與lpClassName 了 ! 但 若 使 用FindWindow 來 傳 送 訊 息 給 現 在 我 們 觀 察 的 這 個 應 用 程 式 , 我 們 到 底應 該 把 訊 息 送 給 誰 呢 ? 是 送 給TApplication 還 是 送 給TForm1 呢? 容 後 在 述 。 先 看 看 是 否 真 的 如 筆 者 所 說TForm1 真 的 是 由TApplication 所掌 管 呢 ? 可 惜C++Builder 所 附 上 的WinSight 並 沒 有 辦 法 查 出TForm1 的 管 理 者 也 就 是 擁 有 者 是 那 個 視 窗 , 所 以 我 們 無 法 由WinSight 看 出 任 何 的 端 倪 , 不 過 幸 好 使 用 筆 者 使 用Visual C++ 所 附 上與WinSight 功 能 相 似 的Spy++ 倒 是 可 以 讓 我 們 瞧 見 所 要 的 證 據( 圖 三 ) : 由 圖 三 很 清 楚 的 可 以 看 出 ,TForm1 的Owner Window 就 是 隱 藏起 來 的TApplication 。 由 以 上 所 蒐 集 的 資 料 來 看TApplication 類別 , 似 乎TApplication 類 別 是 由C++Builder 所 建 立 的 應 用 程 式 中的 大 總 管 , 這 一 切 也 在Project1 的Project1.cpp 裡 透 露 出 這 祕密 : 程 式 列 表 一: 、Project1.cpp :
#pragma hdrstop USERES("Project1.res"); USEFORM("Unit1.cpp", Form1); //-------------------------------------------------------------- WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { try { Application->Initialize(); Application->CreateForm(__classid(TForm1), &Form1); Application->Run(); } catch (Exception &exception) { Application->ShowException(&exception); } return 0; } //--------------------------------------------------------------
進 行Application 物 件 的 初 始 化 , 如 COM 與 OLE Automation
的 初始 化 開 始 建 立 主 視 窗 , 也 就 是Form1 , 並 顯 示Form1 喔 ! 原 來 整 個 由C++Builder 所 建 立 的 應 用 程 式 所 收 到 的 視窗 訊 息 , 都 會 由Application 這 全 域 物 件 來 接 收 , 並 且 透 過訊 息 在VCL 架 構 裡 的 流 動 把 訊 息 分 配 到 該 分 配 的 地 方 去 ,但 僅 止 於 他 認 得 的 訊 息 。 為 什 麼 說 僅 止 於 他 認 得 的 呢 ?這 得 討 論 到VCL 的 內 部 架 構 , 且 聽 筆 者 慢 慢 說 下 去 。
接 下 來 就 讓 我 們 討 論 討 論 在C++Builder 下 接 收 視 窗 訊 息 的做 法 。 在C++Builder 下 有 很 多 做 法 , 咱 們 一 一 來 討 論 : 1 、 覆 載Dispatch 虛 擬 函 式 : ( 使 用Message Map ) 在53 期 的 『BCB 與Win32 訊 息 傳 遞 』 一 文 內 曾 用 到 以 下 的 做法 來 接 收 並 處 理 訊 息 : BEGIN_MESSAGE_MAP VCL_MESSAGE_HANDLER( … …) END_MESSAGE_MAP( …) 但 這 個 機 制 到 底 是 怎 麼 做 成 的 呢 ? 咱 們 把 他 從sysdef.h 裡 給 挖 出 來 瞧 一 瞧 : 程 式 列 表 二 、sysdefs.h :
{ \ 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; \ } \ } A 、BEGIN_MESSAGE_MAP : (601 行 至603 行 ) 這 個 巨 集 在 類 別 的 定 義 中 以 虛 擬 的 方 式 重 新 覆 載 了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 這 一 種 方 法 嗎 ? 不 還 有 很 多 呢 !接 著 看 下 去 。 2 、 覆 載WndProc 函 式 剛 才 談 過TObject::Dispatch 的 做 法 , 再 來 談 談TControl 裡 頭 對於 訊 息 的 處 理 , 在TControl 裡 頭 處 理 訊 息 有 兩 個 一 個 是 由TObject 衍 生 而 來 的Dispatch , 但 在TControl 裡 頭 並 沒 有 複 寫Dispatch 函式 , 依 舊 採 用TObject 裡 頭 的Dispatch 函 式 , 而 另 一 個 方 式 就是WndProc 函 式 , 先 來 瞧 瞧TControl 類 別 裡 頭 的WndProc 做 些 什 麼: 程 式 列 表 三 、controls.pas :
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;
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;
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; 就 好 比TGraphicControl 的 衍 生 類 別 :TImage 類 別 , 當 我 們 把TImage 元 件 被 我 們 置 放 到TWinControl 的 衍 生 類 別TForm1 上 時 ,TForm1 就 是TImage 的Owner , 而Owner 把 該 送 給 其 所 擁 有 的 子 物 件 的 所有 訊 息 給 發 送 出 去 , 如 此 一 來 這 個TImage 就 能 夠 擁 有 處 理滑 鼠 訊 息 的 能 力 了 。 由 以 上 可 知 , 若 要 在 接 收 端 接 收 訊 息 , 除 了 覆 載Dispatch 函 式 外 , 也 可 以 在 接 收 端 重 新 覆 載WndProc 這 個 函 式 來 達 到訊 息 的 處 理 目 的 。 方 法 如 下 :
{ switch(Message.Msg) { case WM_COPYDATA: OnCopyData(Message);// 處 理WM_COPYDATA 的 函 式 break; } TForm::WndProc(Message); } 3 、Application OnMessage 先 前 說 過 ,Application 物 件 是 整 個 應 用 程 式 的 幕 後 黑 手, 由 程 式 列 表 一 的13 行 開 始 進 入 整 個 應 用 程 式 的 訊 息 迴圈 , 一 直 到 收 到WM_QUIT 這 個 訊 息 才 跳 出 迴 圈 並 結 束 程 式 的執 行 。 先 來 看 看TApplication::Run 到 底 是 怎 麼 讓 程 式 進 入 訊 息 迴圈 : 程 式 列 表 四 、forms.pas
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;
begin if not ProcessMessage then Idle; end;
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; 因 此 若 由 所 指 定 的OnMessage 函 式 來 接 收 並 訊 息 , 那 麼 我們 所 收 到 的 訊 息 應 該 就 是 第 一 手 訊 息 , 那 麼 我 們 的 處 理函 式 也 就 是 最 快 速 處 理 的 囉 ! 先 看 看OnMessage 的 原 始 定 義: void __fastcall ApplicationOnMessage(tagMSG &Msg, bool &Handled) 若 你 不 希 望VCL 繼 續 處 理 這 個 訊 息 的 話 , 只 需 把Handled 這個 旗 標 給 設 定 成true 即 可 。 就 讓 筆 者 舉 個 簡 單 例 子 :
{ 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; } } //-------------------------------------------------------------- 以 先 前 建 立 的 那 個 什 麼 都 沒 做 的 應 用 程 式 為 例 , 使 用FindWindow 方 式 來 取 得 視 窗 代 碼 有 兩 種 方 法 : A 、 用FindWindow("TApplication","Project1"); 來 取 得Application->Handle 。 B 、 用FindWindow("TForm1","Form1"); 來 取 得Form1->Handle 。
當 程 式 執 行 到Application->Run() 時 就 進 入 到 訊 息 處 理 迴 圈裡 , 我 們 的 應 用 程 式 透 過PeekMessage 這 個API 函 式 由 訊 息 佇列 裡 取 得 發 給 本 應 用 程 式 的 訊 息 , 並 將 此 訊 息 從 訊 息 佇列 裡 移 除 , 之 後 把 訊 息 交 給TApplication::ProcessMessage() 來 將訊 息 分 派 給 該 收 到 的 物 件 上 。 但 若 指 定 了TApplication 類 別裡 的OnMessage 事 件 時 , 會 先 將 訊 息 引 導 到 指 定 的OnMessage 函式 上 , 經 過OnMessage 函 式 處 理 過 後 的 訊 息 才 流 到 訊 息 迴 圈裡 。 訊 息 經 過TApplication::ProcessMessage() 的 分 派 後 , 訊 息 會先 流 到StdWndProc , 而StdWndProc 的 工 作 就 是 將 訊 息 推 進 到WndProc 函 式 裡 做 處 理 , 進 入 到WndProc 函 式 後 被WndProc 所 認 得 的 就優 先 被 處 理 , 最 後 不 認 得 再 交 由 覆 載 的Dispatch 函 式 處 理, 若 仍 無 法 處 理 則 交 給 父 類 別 的Dispatch 做 處 理 , 而Dispatch 函 式 最 後 會 把 訊 息 推 動 到DefaultHandler 函 式 裡 去 處 理 。 嗯! 又 學 到 了 一 點 , 其 實 也 可 以 重 新 覆 載DefaultHandler 來 處理 訊 息 , 但 這 屬 於 訊 息 接 收 最 後 的 底 限 , 要 從 這 裡 才 接收 嗎 ? 隨 便 你 囉 ! 根 據 這 張 訊 息 流 通 圖 來 看 , 這 三 種 方 法 所 接 收 到 的 訊息 先 後 為 :
覆 載 的WinProc 覆 載 的Dispatch
1 、 訊 息 發 出 端 先 來 設 計 一 個 簡 單 板 的 訊 息 發 出 端 , 放 置 兩 個Button 元件 以 及 一 個Edit 元 件 到Form1 上 面 , 其 擺 設 如 圖 六 。 為Clear 按 鈕 加 上OnClick 事 件 , 用 來 清 除Edit1 元 件 上 的 文字 :
{ Edit1->Text = “ ”;// 清 除Edit1->Text 的 內 容 }
{ 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; }
WM_COPYDATA, (WPARAM)NULL, (LPARAM)pcp); 2 、 訊 息 接 收 端 ─ TraceWin : OK! 剛 剛 已 經 把 簡 單 版 本 的 訊 息 發 出 端 給 設 置 好 , 接 下來 就 是 要 把 接 收 端 給 搞 定 了 。 先 做 一 個 簡 單 的 接 收 端 來接 收 由 發 出 端 所 發 出 的 文 字 訊 息 並 秀 出 來 , 所 以 在Form1 上 放 置 一 個Memo ( 如 圖 七 ) , 且 設 定Memo 的Align 屬 性 為alClient , 並 把 其 中Lines 的 內 容 給 刪 除 。 但 是 要 記 住 , 我 們 先 得把Form1 的Caption 給 設 定 成 『TRACE Window 』 讓 發 出 端 能 夠 正 確使 用FindWindow 來 取 得 接 收 端 的 視 窗 代 碼 。 另 外 為 了 順 便 是一 下 這 三 種 接 收 訊 息 的 方 法 , 也 要 記 得 把 到Project Option 裡 把Application 的Title 給 改 成 『TRACE Window 』 ( 如 圖 八 ) , 或是 在Project1.cpp 裡 頭 加 上 指 定Application->Title 的 程 式 碼 , 如下 :
Application->Title = “TRACE Window ”; Application->CreateForm(__classid(TForm1), &Form1); 在Unit1.h 裡 加 入 : private: // User declarations void __fastcall OnCopyData(TMessage &Msg); 在Unit1.cpp 裡 加 入 :
{ PCOPYDATASTRUCT pcp; pcp = (PCOPYDATASTRUCT)Msg.LParam; char* TraceMsg; TraceMsg = (char*)pcp->lpData; Memo1->Lines->Add(TraceMsg);// 將 接 收 到 的 陣 列 一 行 一 行加 到 給 到Memo1 裡 頭 } A 、 『 覆 載Dispatch 函 式 』 在unit1.h 裡 加 入 :
BEGIN_MESSAGE_MAP VCL_MESSAGE_HANDLER(WM_COPYDATA,TMessage,OnCopyData) END_MESSAGE_MAP(TForm) B 、 『 覆 載WndProc 函 式 』 在unit1.h 裡 加 入 :
void __fastcall WndProc(TMessage &Message);
{ switch(Message.Msg) { case WM_COPYDATA: OnCopyData(Message);// 處 理WM_COPYDATA 的 函 式 break; } TForm::WndProc(Message); } C 、 『 指 定OnMessage 函 式 』 在unit1.h 裡 加 入 :
void __fastcall ApplicationOnMessage(tagMSG &Msg, bool &Handled);
{ Application->OnMessage = ApplicationOnMessage; } //-------------------------------------------------------------- void __fastcall TForm1::ApplicationOnMessage(tagMSG &Msg, bool &Handled) { switch(Msg.message) { case WM_COPYDATA:
Message.Msg = Msg.message; Message.WParam = Msg.wParam; Message.LParam = Msg.lParam;
Handled = true; break; } } //-------------------------------------------------------------- 不 過 誠 如 先 前 所 提 到 的 , 採 用 這 種 做 法 相 對 的 在 訊 息發 出 端 就 得 把 訊 息 送 給Application 物 件 而 不 是 送 給Form1 。 發出 端 的FindWindow 應 該 是 ;FindWindow("TApplication","TRACE Window") , 這 樣 才 能 夠 取 得Application->Handle 。 否 則 若 把 訊 息 直 接 發給Application 而 你 在Form1 上 頭 攔 截 訊 息 是 絕 對 攔 截 不 到 的 ,反 之 亦 然 。 3 、 訊 息 收 發 測 試 : OK ! 已 經 完 成 了 大 半 , 先
來 測 試一 下 吧 ( 圖 九 ) :
從 物 件 檢 視 器 (Object Inspector ) 上 頭 改 就 可 以 了 , 找 到原 先TForm1 的Name 屬 性 欄 把 他 由Form1 改 為MainForm 則 繼 承 至TForm 類 別 的TForm1 類 別 則 會 變 為TMainForm 類 別 ( 圖 十 ) 。 也 因 此 原 先 所 使 用 的FindWindow 要 改 成FindWindow( “TMainForm ”, ”TRACE Window ”) 。 B 、TRACE Window 的 功 能 加 強 嗯 , 改 好 了 視 窗 類 別 後 還 缺 什 麼 呢 ? 缺 的 可 多 了 ! 如把Memo1 元 件 上 的 文 字 給 存 檔 起 來 以 便 日 後 觀 看 、 清 除Memo1 元 件 上 的 內 容 或 是 增 加 進 制 轉 換 等 等 的 功 能 , 說 都 說 不完 。 這 些 都 是 應 該 且 必 須 的 , 該 怎 麼 做 呢 ? 這 些 功 能 筆者 在 此 僅 舉 幾 個 簡 單 的 說 明 , 不 再 詳 述 : a 、Memo1 元 件 上 的 文 字 存 檔 : 在VCL 架 構 中 , 所 有
的VCL 元件 都 被 包 裝 的 非 常 完 整 , 所 以 做 起 例 行
工 作 來 非 常 的 容易 , 要 將Memo1 元 件 上 的 資 料 存 檔
只 需 如 下 : 這 樣 就 會 把 資 料 存 到D:\trace.txt 裡 頭 了 。 不 過 這 樣 似 乎 有 點 太 過 簡 單 , 咱 們 再 加 上 一 個 存 檔 對話 盒 吧 :
SD->Options << ofOverwritePrompt;// 增 加 檔 案 覆 蓋 時的 確 認 功 能 SD->Filter = "Text files (*.txt)|*.TXT"; if (SD->Execute()) { Memo1->Lines->SaveToFile(SD->FileName); } delete SD; b 、 清 除Memo1 上 的 文 字 : 清 除Memo1 上 的 文 字 筆 存 檔 更 容 易 , 只 需 使 用Clear()
這 個成 員 函 式 , 如 下 : Memo1->Clear(); 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 <windows.h> #ifndef __STDIO_H #include <stdio.h> #endif /* __STDIO_H */ #ifndef __WCHAR_H #include <wchar.h> #endif /* __WCHAR_H */ #define SMSG(X) SendMessage(FindWindow("TMainForm","TRACE Window") ,\ WM_COPYDATA, (WPARAM)NULL,(LPARAM)X); #define DebugOut(X) \ { \ COPYDATASTRUCT *pcp = new COPYDATASTRUCT;\ pcp->dwData = 0;\ pcp->cbData = sizeof(X);\ pcp->lpData = &X;\ SMSG(pcp);\ delete pcp;\ } void douth(long Data)//for Tracing Hex Value { char TM[255]; sprintf(TM,"0x%x",Data); DebugOut(TM); } void dout(char* Data) { char TM[255]; sprintf(TM,"%s",Data); DebugOut(TM); } void dout(WCHAR* Data) //for Tracing Unicode string { char TM[255]; #ifdef DSTRING_H AnsiString AnsiData = AnsiString(Data); sprintf(TM,"%s",AnsiData.c_str()); #else sprintf(TM,"%ls",Data); #endif /* DSTRING_H */ DebugOut(TM); } #ifdef DSTRING_H void dout(AnsiString* Data)//for Tracing AnsiString BCB only { char TM[255]; sprintf(TM,"%s",Data->c_str()); DebugOut(TM); } void dout(AnsiString Data) //for Tracing AnsiString BCB only { char TM[255]; sprintf(TM,"%s",Data.c_str()); DebugOut(TM); } #endif /* DSTRING_H */ #endif 以 下 是 在Delphi 下 使 用 時 的 程 式 碼 : 程 式 列 表 六 、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)
// 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.
註 一 、 與IPC 主 題 相 關 的 書 籍 有 : 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 函 式 來 向 系 統 註 冊 。 |
|
首 頁 | 如
何 購 買 | 產
品 訊 息 | 軟
體 下 載 | 案
例 研 究 | 技
術 支 援 | 技
術 白 皮 書 推 薦 文 章 | 參 考 書 籍 | 研 討 會 訊 息 | 訓 練 課 程 | 全 球 據 點 | 隱 私 權 宣 告 | Y2K . |