如何检测Windows平台是否有应用正在捕获屏幕?针对SetWindowDisplayAffinity存在bug的旧版本,如何通过P/Invoke手动检测并实现窗口遮挡?
Great question—let's break this down step by step since you're dealing with a common pain point: working around SetWindowDisplayAffinity bugs in older Windows versions while maintaining anti-capture functionality.
1. Detecting Screen Capture Activity (or if a Specific App is Being Captured)
There are a few reliable approaches here, depending on the Windows version you're targeting:
For Windows 10 1809+ & Windows 11
Microsoft added official APIs to detect screen capture events, which is the most robust method:
- Use
RegisterScreenCaptureNotificationto register a callback that triggers when a screen capture session starts or ends. This will notify your app whenever any process initiates capture (screenshots, screen recording, etc.). - To check if a specific window is being captured, combine this with
SetWinEventHooklistening forEVENT_SYSTEM_CAPTURESTARTandEVENT_SYSTEM_CAPTUREENDevents. You can map these events to the target window usingGetWindowThreadProcessId.
Here's a quick P/Invoke snippet to get you started with the notification:
[DllImport("user32.dll", SetLastError = true)] private static extern IntPtr RegisterScreenCaptureNotification(IntPtr hWnd, IntPtr callback); // Your callback method signature private void OnScreenCaptureNotification(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { // wParam = 1 when capture starts, 0 when it ends bool isCapturing = wParam != IntPtr.Zero; // Handle capture state changes here }
For Older Windows Versions (Pre-1809)
Official APIs don't exist here, so you'll have to use heuristic methods:
- Check running processes for known capture tools (OBS, Snagit, ShareX, etc.)—note this isn't foolproof, as custom capture tools won't be detected.
- Periodically compare the target window's expected pixel data with its actual rendered pixels (a hacky approach, but works for specific use cases).
2. Manual Blackout Without System Anti-Capture APIs
If you need to bypass SetWindowDisplayAffinity bugs, creating a custom overlay window is a solid fallback. Here's how to implement it in C#:
Core Approach
Create a topmost, black, input-transparent window that perfectly overlays your target window. Capture tools will only see the black overlay, but your original window still receives user input.
Step-by-Step Implementation
- Set Window Styles: Use extended styles to make the overlay topmost, layered, and transparent to input.
- Sync with Target Window: Listen for the target window's position/size changes to update the overlay in real time.
- Handle State Changes: Hide the overlay when the target is minimized, close it when the target closes.
Here's a code example:
using System; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; public class BlackoutOverlay : Form { private readonly IntPtr _targetWindowHandle; public BlackoutOverlay(IntPtr targetWindowHandle) { _targetWindowHandle = targetWindowHandle; // Basic window setup FormBorderStyle = FormBorderStyle.None; ShowInTaskbar = false; StartPosition = FormStartPosition.Manual; BackColor = Color.Black; // Set extended styles for topmost, layered, and input transparency SetWindowLongPtr(Handle, GWL_EXSTYLE, GetWindowLongPtr(Handle, GWL_EXSTYLE) | WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TRANSPARENT); // Make the window fully opaque SetLayeredWindowAttributes(Handle, 0, 255, LWA_ALPHA); // Sync initial position and size UpdateOverlayPosition(); // Use a timer to keep overlay synced (replace with WinEvent hooks for better performance) var syncTimer = new Timer { Interval = 100 }; syncTimer.Tick += (s, e) => UpdateOverlayPosition(); syncTimer.Start(); } private void UpdateOverlayPosition() { if (!IsWindowVisible(_targetWindowHandle)) { Hide(); return; } Show(); GetWindowRect(_targetWindowHandle, out RECT windowRect); Bounds = new Rectangle(windowRect.Left, windowRect.Top, windowRect.Right - windowRect.Left, windowRect.Bottom - windowRect.Top); } // P/Invoke definitions private const int GWL_EXSTYLE = -20; private const uint WS_EX_TOPMOST = 0x00000008; private const uint WS_EX_LAYERED = 0x00080000; private const uint WS_EX_TRANSPARENT = 0x00000020; private const uint LWA_ALPHA = 0x00000002; [StructLayout(LayoutKind.Sequential)] private struct RECT { public int Left, Top, Right, Bottom; } [DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); [DllImport("user32.dll")] private static extern bool SetLayeredWindowAttributes(IntPtr hWnd, uint crKey, byte bAlpha, uint dwFlags); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd); } // Usage example: // var overlay = new BlackoutOverlay(targetWindowHandle); // overlay.Show();
Key Notes
- This overlay blocks most screen capture tools that capture the desktop frame buffer, but won't stop tools that read directly from the GPU (like some game capture tools).
- For better performance, replace the timer with
SetWinEventHooklistening forEVENT_OBJECT_LOCATIONCHANGEevents on the target window—this triggers updates only when the window actually moves or resizes.
内容的提问来源于stack exchange,提问作者vkolesnik

