Windows CE 提供 SetWindowLong API,允許將視窗的原始訊息處理函數(Window Procedure)替換為自訂函數,實現「視窗子類化」。這對於攔截特定按鍵、修改控件行為等場景極為實用。
| API | 用途 |
|---|---|
SetWindowLong(hWnd, GWL_WNDPROC, NewProc) | 設定新視窗程序,返回舊程序位址 |
GetWindowLong(hWnd, GWL_WNDPROC) | 取得目前視窗程序位址 |
CallWindowProc(PrevProc, hWnd, Msg, wParam, lParam) | 呼叫舊視窗程序,維持預設行為 |
class CMainWnd : public CWndBase {
protected:
static LRESULT CALLBACK CtrlProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
private:
HWND m_hEdit;
WNDPROC m_pOldEditProc;
};
BOOL CMainWnd::Create(HINSTANCE hInst, HWND hParent, ...) {
// ... 基礎建立略 ...
// 建立 Edit 控件
m_hEdit = CreateWindowEx(WS_EX_TOPMOST, TEXT("EDIT"), TEXT(""),
ES_LEFT | WS_TABSTOP | WS_VISIBLE | WS_CHILD | WS_BORDER,
cx/3, cy/3, cx/3, cy/3, m_hWnd, (HMENU)IDC_EDIT_WORD, m_hInst, NULL);
// 將 this 指標存入 GWL_USERDATA,供靜態回呼函數取得物件實例
SetWindowLong(m_hEdit, GWL_USERDATA, (DWORD)this);
// 保存原視窗程序,設定新程序
m_pOldEditProc = (WNDPROC)GetWindowLong(m_hEdit, GWL_WNDPROC);
SetWindowLong(m_hEdit, GWL_WNDPROC, (DWORD)CtrlProc);
return TRUE;
}
LRESULT CALLBACK CMainWnd::CtrlProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
// 取得儲存的物件指標
CMainWnd* pThis = (CMainWnd*)GetWindowLong(hWnd, GWL_USERDATA);
if (!pThis || pThis->m_hEdit != hWnd)
return DefWindowProc(hWnd, uMsg, wParam, lParam);
switch (uMsg) {
case WM_CHAR:
if ((TCHAR)wParam == VK_ESCAPE) {
PostQuitMessage(0); // 按 ESC 退出程式
return 0;
}
break;
}
// 其他訊息交給原視窗程序處理
return CallWindowProc(pThis->m_pOldEditProc, hWnd, uMsg, wParam, lParam);
}
// SetWindowLong 返回值即為舊程序位址
m_pOldEditProc = (WNDPROC)SetWindowLong(m_hEdit, GWL_WNDPROC, (DWORD)CtrlProc);
若將視窗程序指向已釋放的記憶體(如 DLL 卸載後、物件銷毀後),系統會立即崩潰。切勿在全域視窗(如前景視窗)上嘗試此操作:
// 危險示範:會導致系統當機
LRESULT MyProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(...) {
HWND hWnd = GetForegroundWindow();
SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)MyProc); // 系統崩潰!
return 0;
}
CallWindowProc 轉發未處理訊息。GWL_USERDATA 傳遞物件實例,解決靜態回呼函數無法存取成員變數的問題。WM_DESTROY)務必呼叫 SetWindowLong 恢復原視窗程序,防止野指標。