Windows 螢幕文字繪製最終均經由 GDI32.dll 核心函數:
TextOutA / TextOutWExtTextOutA / ExtTextOutW取詞關鍵:**攔截這些函數調用,提取參數中的字串指標、座標、長度**。
// 全域鉤子 (WH_MOUSE) 記錄滑鼠位置
// 或定時器輪詢 GetCursorPos
POINT pt; GetCursorPos(&pt);
HWND hTarget = WindowFromPoint(pt);
if (!hTarget) return;
ScreenToClient(hTarget, &pt); // 轉客戶區座標
// 使目標區域失效,迫使 WM_PAINT → TextOut
InvalidateRect(hTarget, NULL, TRUE);
UpdateWindow(hTarget); // 同步發送 WM_PAINT
// 原函數指標
typedef BOOL (WINAPI *PFN_TextOutA)(HDC, int, int, LPCSTR, int);
PFN_TextOutA g_RealTextOutA = NULL;
// Hook 函數
BOOL WINAPI MyTextOutA(HDC hdc, int x, int y, LPCSTR lpString, int c)
{
// 1. 業務邏輯:記錄/翻譯/顯示提示
if (lpString && c > 0) {
String text(lpString, c); // 處理 ANSI 字串
// 送往主程式顯示翻譯...
PostMessage(g_hMainWnd, WM_USER_TEXTOUT, (WPARAM)hdc, (LPARAM)text.c_str());
}
// 2. 呼叫原函數保證正常繪製
return g_RealTextOutA(hdc, x, y, lpString, c);
}
SetWindowsHookEx 安裝時,系統將 DLL 映射到**所有受影響進程**的位址空間。TextOutA 仍指向 GDI32,需進一步修改 IAT 或使用 Inline Hook。
// 遍歷目標模組導入表,找到 gdi32.dll 的 TextOutA 項,修改函數指標
PIMAGE_IMPORT_DESCRIPTOR pImport = ...; // 定位 gdi32.dll 導入描述符
PIMAGE_THUNK_DATA pThunk = ...; // 遍歷 Thunk
while (pThunk->u1.Function) {
if (IsTargetFunction(pThunk, "TextOutA")) {
// 修改頁面保護
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(pThunk, &mbi, sizeof(mbi));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &mbi.Protect);
// 保存原指標、寫入新指標
g_RealTextOutA = (PFN_TextOutA)pThunk->u1.Function;
pThunk->u1.Function = (DWORD)MyTextOutA;
// 恢復保護
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &mbi.Protect);
break;
}
pThunk++;
}
| 難點 | 對策 |
|---|---|
| Unicode/ANSI 混用 | 同時 Hook TextOutW、ExtTextOutW,統一轉 UTF-16 處理 |
| DirectWrite / GDI+ 繞過 GDI | 現代應用多用 DirectWrite,需額外 Hook IDWriteTextLayout::Draw 或使用 UI Automation |
| 效能:頻繁調用導致卡頓 | 快速判斷是否目標進程/視窗,非目標直接跳轉原函數;批次傳遞字串 |
| 64 位進程 | 編譯 64 位 Hook DLL,或使用 Wow64 機制 |
| 安全軟體攔截 | 簽名驗證、白名單、避免修改系統關鍵進程 |
// 不需 Hook,直接查詢文字模式
IUIAutomation *pAuto; CoCreateInstance(CLSID_CUIAutomation, ..., &pAuto);
IUIAutomationElement *pElem; pAuto->ElementFromPoint(pt, &pElem);
IUIAutomationTextPattern *pTextPattern;
pElem->GetCurrentPattern(UIA_TextPatternId, (IUnknown**)&pTextPattern);
IUIAutomationTextRange *pRange; pTextPattern->DocumentRange(&pRange);
BSTR text; pRange->GetText(-1, &text); // 獲取完整文字
// 支援 Chrome, Edge, Office, WPF 等現代 UI
滑鼠移動 → Hook 記錄座標
↓
目標視窗 InvalidateRect → WM_PAINT
↓
目標進程呼叫 TextOutA (GDI32)
↓
Hook DLL (已注入) 截獲 MyTextOutA
↓
提取字串 → PostMessage 給主程式
↓
主程式顯示翻譯/字典視窗
↓
呼叫原 TextOutA 完成繪製

GDI Hook 取詞是經典技術,但面對現代渲染引擎 (DirectWrite, GPU 加速) 效力遞減;新專案建議優先採用 UI Automation 或 OCR (Tesseract/Windows OCR API)。