众所周知Windows API GetWindowText/GetWindowTextLength有点坑,可能会耗时久以及造成当前进程卡死。最近调查客户卡死问题,唯一可疑的线程就在调用GetWindowText,于是决定先重定向这个系统API到自己实现的不卡顿的版本。思路是在客户用的自己DLL被加载时,直接修改进程的内存中的代码即可~不再赘述,上代码
int WINAPI internalGetWindowTextLengthA(HWND hWnd) { return 50; }
int WINAPI internalGetWindowTextLengthW(HWND hWnd) { return 50; }
int WINAPI internalGetWindowTextA(HWND hWnd, LPSTR lpString, int nMaxCount) {
if (!lpString || !nMaxCount) return 0;
LPWSTR lpWString = (LPWSTR) malloc(nMaxCount * sizeof(WCHAR));
if (!lpWString) return 0;
int textLength = InternalGetWindowText(hWnd, lpWString, nMaxCount);
if (!textLength) {
free(lpWString);
return 0;
}
int ansiTextLength =
WideCharToMultiByte(CP_ACP, 0, lpWString, -1, NULL, 0, NULL, NULL);
ansiTextLength =
WideCharToMultiByte(CP_ACP, 0, lpWString, textLength, lpString,
min(nMaxCount, ansiTextLength), NULL, NULL);
free(lpWString);
return ansiTextLength;
}
int WINAPI internalGetWindowTextW(HWND hWnd, LPWSTR lpString, int nMaxCount) {
return InternalGetWindowText(hWnd, lpString, nMaxCount);
}
// This will use ::GetProcAddress to get the original function and then use jmp to redirect the call to the new function
bool hookSysFunctions(HMODULE hModule, LPCSTR lpProcName, FARPROC func) {
FARPROC pfnOrig = GetProcAddress(hModule, lpProcName);
if (!pfnOrig) return 0;
#ifdef _WIN64
DWORD oldProtect;
VirtualProtect(pfnOrig, 14, PAGE_EXECUTE_READWRITE, &oldProtect);
*(BYTE *) pfnOrig = 0x48;
*(BYTE *) ((DWORD_PTR) pfnOrig + 1) = 0xB8;
*(DWORD_PTR *) ((DWORD_PTR) pfnOrig + 2) = (DWORD_PTR) func;
*(BYTE *) ((DWORD_PTR) pfnOrig + 10) = 0xFF;
*(BYTE *) ((DWORD_PTR) pfnOrig + 11) = 0xE0;
VirtualProtect(pfnOrig, 14, oldProtect, &oldProtect);
return true;
#else
DWORD oldProtect;
VirtualProtect(pfnOrig, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
*(BYTE *) pfnOrig = 0xE9;
*(DWORD *) ((DWORD_PTR) pfnOrig + 1) =
(DWORD_PTR) func - (DWORD_PTR) pfnOrig - 5;
VirtualProtect(pfnOrig, 5, oldProtect, &oldProtect);
return true;
#endif
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL,// handle to DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpvReserved)// reserved
{
// Perform actions based on the reason for calling.
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
// Initialize once for each new process.
// Return FALSE to fail DLL load.
{
std::vector<std::pair<std::string, FARPROC>> internalFunctions = {
{"GetWindowTextLengthA", (FARPROC) internalGetWindowTextLengthA},
{"GetWindowTextLengthW", (FARPROC) internalGetWindowTextLengthW},
{"GetWindowTextA", (FARPROC) internalGetWindowTextA},
{"GetWindowTextW", (FARPROC) internalGetWindowTextW}};
HMODULE hModule = GetModuleHandleW(L"user32");
for (auto &func : internalFunctions) {
hookSysFunctions(hModule, func.first.c_str(), func.second);
}
}
break;
case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;
case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;
case DLL_PROCESS_DETACH:
if (lpvReserved != nullptr) {
break;// do not do cleanup if process termination scenario
}
// Perform any necessary cleanup.
break;
}
return TRUE;// Successful DLL_PROCESS_ATTACH.
}
验证下来果然不卡了,而且调试发现这个线程就是客户flutter application的ui线程,使用了win32这个flutter的库。一般团队不建议用这几个函数去枚举窗口获取窗口信息,坑比较多。