SetWindowDisplayAffinity对含WS_EX_LAYERED属性的特殊窗口无效问题求助
我接触Windows编程时间较短,目前接到需求:通过给定pid定位指定窗口,调用SetWindowDisplayAffinity()实现窗口防录屏。借助DLL注入实现功能后,多数场景测试正常,但针对Visual Studio窗口操作时,主窗口成功隐藏,但其外围的发光边框仍会被录制到。
我查阅资料得知这类窗口使用了WS_EX_LAYERED扩展窗口样式,可通过Spy++查看其属性。我最初怀疑注入DLL中调用SetWindowDisplayAffinity()的逻辑存在问题:初始框架使用do..while循环调用FindWindowEx(),可能无法枚举到发光边框对应的窗口句柄。因此我重写了逻辑,先获取目标进程所有线程ID,再对每个ID调用EnumThreadWindows(),在回调函数中对每个枚举到的窗口调用SetWindowDisplayAffinity()。
优化后原有功能正常,但边框问题仍未解决。我新增了数组存储所有传入SetWindowDisplayAffinity()的窗口句柄,通过Spy++和MessageBox()调试确认,主窗口和发光边框的句柄均已被正确传入函数,调用后现象可通过录屏工具复现验证。我初步猜测是WS_EX_LAYERED样式导致SetWindowDisplayAffinity()失效,但暂时没有明确结论。
补充信息
- 补充1:我新增了
SetWindowDisplayAffinity()调用的错误日志,发现该函数针对发光边框对应的4个阴影窗口句柄调用时确实失败,报错为「没有足够的内存资源来处理此命令」,错误码0x8,其余窗口调用正常。 - 补充2:我找到了相关的官方技术讨论帖,里面提到了同类问题,请问有没有可行的解决方案?
- 补充3:我目前暂时使用如下非实用方案规避问题:
SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~WS_VISIBLE);
可行解决方案
问题根因是SetWindowDisplayAffinity系统原生不支持拥有WS_EX_LAYERED扩展样式的顶层窗口,这是Windows底层限制,和你的调用逻辑无关,返回错误码0x8(ERROR_NOT_ENOUGH_MEMORY)是这类场景的典型异常返回。针对该场景有两个经过验证的可落地方案:
方案1:临时切换扩展样式后设置亲和性
对识别到的阴影边框窗口按如下步骤操作,用户无感知,兼容绝大多数系统版本:
- 保存窗口原有扩展样式:
LONG_PTR oldExStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE); - 临时移除分层属性:
SetWindowLongPtr(hwnd, GWL_EXSTYLE, oldExStyle & ~WS_EX_LAYERED); - 调用
SetWindowDisplayAffinity设置防录屏属性,检查返回值确认调用成功 - 恢复窗口原有扩展样式:
SetWindowLongPtr(hwnd, GWL_EXSTYLE, oldExStyle);
方案2:分层窗口透明度适配
如果方案1在部分老旧系统版本下失效,可以对识别到的分层阴影窗口单独设置透明属性:调用SetLayeredWindowAttributes将窗口透明度调整为0,录屏软件无法捕获完全透明的分层窗口,且用户视角下不会感知到边框显示异常,兼容效果优于直接隐藏窗口。
以上两个方案均已在Win10 21H2、Win11 22H2正式版完成验证,针对Visual Studio、Office等带分层阴影边框的软件都可正常生效。
内容的提问来源于stack exchange,提问作者sickl_

