SetWindowDisplayAffinity导致Electron窗口录屏黑屏的问题求助
问题描述
我通过DLL注入在其他进程中调用SetWindowDisplayAffinity,传入WDA_EXCLUDEFROMCAPTURE参数让窗口在录屏软件中完全隐藏。初始状态下功能正常,但当目标Electron应用最小化到托盘后再显示窗口时,录屏软件会看到黑屏(效果类似WDA_MONITOR)。
系统环境
Windows 11 Pro 23H2
我的代码
#include "library.h" #include <vector> #include <windows.h> auto createdWindows = std::vector<HWND>(); BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) { DWORD windowPID = 0; GetWindowThreadProcessId(hwnd, &windowPID); DWORD currentPID = GetCurrentProcessId(); if (windowPID == currentPID) { SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); } return TRUE; } VOID CALLBACK TimerProc(HWND hwnd, UINT message, UINT idTimer, DWORD dwTime) { for (auto hWnd : createdWindows) { SetWindowDisplayAffinity(hWnd, WDA_EXCLUDEFROMCAPTURE); } createdWindows.clear(); } void StartTimer() { SetTimer(nullptr, 0, 1000, reinterpret_cast<TIMERPROC>(TimerProc)); } LRESULT CALLBACK WindowProcHook(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HCBT_CREATEWND) { HWND hwnd = (HWND)wParam; SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); } return CallNextHookEx(nullptr, nCode, wParam, lParam); } void SetWindowHook() { SetWindowsHookExA(WH_CBT, WindowProcHook, nullptr, GetCurrentThreadId()); } BOOL WINAPI DllMain( HINSTANCE hinstDLL, // handle to DLL module DWORD fdwReason, // reason for calling function LPVOID lpvReserved ) // reserved { EnumWindows(EnumWindowsProc, 0); SetWindowHook(); StartTimer(); return TRUE; }
解决方案
问题根源
Electron应用从托盘恢复时,窗口的显示亲和性(Display Affinity)会被系统或框架重置;现有逻辑仅覆盖了窗口创建和初始枚举场景,未处理窗口状态变化(如从托盘恢复)的情况。另外,WH_CBT钩子仅挂在当前线程,Electron多线程创建窗口时可能遗漏,1秒间隔的定时器也会导致窗口恢复后出现短暂黑屏。
修复步骤
- 监听窗口状态变化:通过
WH_CALLWNDPROC钩子捕获WM_SHOWWINDOW、WM_ACTIVATE等消息,在窗口恢复显示时重新设置WDA_EXCLUDEFROMCAPTURE。 - 覆盖所有线程的钩子:枚举目标进程的所有线程并逐个挂钩子,确保捕获Electron多线程创建的窗口消息。
- 优化定时器逻辑:定时器触发时重新枚举当前进程所有窗口并设置亲和性,避免遗漏状态变化的窗口。
- 重置窗口状态:窗口恢复时先设
WDA_NONE清除异常状态,再重新设置目标亲和性,确保状态正确。
修改后的代码示例
#include "library.h" #include <vector> #include <windows.h> #include <tlhelp32.h> // 枚举当前进程的所有线程ID std::vector<DWORD> GetProcessThreads(DWORD pid) { std::vector<DWORD> threads; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hSnapshot == INVALID_HANDLE_VALUE) return threads; THREADENTRY32 te; te.dwSize = sizeof(THREADENTRY32); if (Thread32First(hSnapshot, &te)) { do { if (te.th32OwnerProcessID == pid) { threads.push_back(te.th32ThreadID); } } while (Thread32Next(hSnapshot, &te)); } CloseHandle(hSnapshot); return threads; } // 窗口消息钩子,捕获显示/激活相关消息 LRESULT CALLBACK WindowMessageHook(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode >= 0) { CWPSTRUCT* msg = reinterpret_cast<CWPSTRUCT*>(lParam); // 窗口显示、激活时重新设置亲和性 if (msg->message == WM_SHOWWINDOW || msg->message == WM_ACTIVATE) { if (msg->wParam != 0) { SetWindowDisplayAffinity(msg->hwnd, WDA_NONE); SetWindowDisplayAffinity(msg->hwnd, WDA_EXCLUDEFROMCAPTURE); } } } return CallNextHookEx(nullptr, nCode, wParam, lParam); } // 枚举当前进程所有窗口并设置亲和性 void SetAllWindowsAffinity() { DWORD currentPID = GetCurrentProcessId(); EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL { DWORD windowPID = 0; GetWindowThreadProcessId(hwnd, &windowPID); if (windowPID == reinterpret_cast<DWORD>(lParam)) { SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); } return TRUE; }, reinterpret_cast<LPARAM>(currentPID)); } VOID CALLBACK TimerProc(HWND hwnd, UINT message, UINT idTimer, DWORD dwTime) { SetAllWindowsAffinity(); } void StartTimer() { // 缩短间隔,减少黑屏持续时间 SetTimer(nullptr, 0, 500, reinterpret_cast<TIMERPROC>(TimerProc)); } // 给当前进程所有线程挂消息钩子 void SetGlobalHooks() { DWORD currentPID = GetCurrentProcessId(); auto threads = GetProcessThreads(currentPID); HINSTANCE hModule = GetModuleHandleA(nullptr); for (DWORD tid : threads) { SetWindowsHookExA(WH_CALLWNDPROC, WindowMessageHook, hModule, tid); } } BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) { // 仅在DLL注入进程时执行初始化 if (fdwReason == DLL_PROCESS_ATTACH) { SetAllWindowsAffinity(); SetGlobalHooks(); StartTimer(); } return TRUE; }
关键说明
GetProcessThreads函数枚举当前进程所有线程,确保钩子覆盖Electron的多线程窗口创建场景。- 钩子监听窗口显示/激活消息,通过先重置再设置的方式避免状态残留。
- 定时器逻辑改为枚举所有窗口,覆盖所有可能遗漏的窗口状态变化。
- DllMain中仅在
DLL_PROCESS_ATTACH时执行初始化,避免重复触发导致的异常。
内容的提问来源于stack exchange,提问作者Rabbit0w0
相关产品推荐
相关产品推荐

