C#调用DXGI截屏时,多显示器数据和会话锁定的那些‘坑’怎么填?(含DesktopDuplication.dll避坑指南)
C#调用DXGI截屏实战多显示器处理与会话锁定的深度解决方案在构建需要长时间稳定运行的屏幕监控或自动化测试工具时DXGI截屏技术因其高性能和低CPU占用率成为首选方案。然而实际开发中会遇到多显示器数据处理、系统会话锁定等棘手问题。本文将分享一套经过实战检验的解决方案帮助开发者避开这些坑。1. 多显示器数据流的正确处理处理多显示器环境是DXGI截屏的第一个挑战。不同物理屏幕返回的数据需要通过ScreenNumber正确区分和处理。1.1 显示器识别与数据映射首先需要建立显示器索引与实际屏幕的对应关系public class DisplayInfo { public int Index { get; set; } public string DeviceName { get; set; } public Rectangle Bounds { get; set; } public bool IsPrimary { get; set; } } public static ListDisplayInfo GetDisplays() { var displays new ListDisplayInfo(); foreach (var screen in Screen.AllScreens) { displays.Add(new DisplayInfo { Index displays.Count, DeviceName screen.DeviceName, Bounds screen.Bounds, IsPrimary screen.Primary }); } return displays; }1.2 多显示器数据分发机制在回调函数中我们需要根据ScreenNumber将数据分发到对应的处理管道private static ConcurrentDictionaryint, ActionBitmap _displayHandlers new ConcurrentDictionaryint, ActionBitmap(); public static void CallBackFunction(IntPtr Image, int width, int height, int RowPitch, int ScreenNumber) { if (_displayHandlers.TryGetValue(ScreenNumber, out var handler)) { using (var bitmap new Bitmap(width, height, RowPitch, PixelFormat.Format32bppRgb, Image)) { handler(bitmap); } } else { // 新显示器连接时的处理逻辑 OnNewDisplayConnected(ScreenNumber); } }2. 会话锁定检测与异常恢复系统会话锁定(如锁屏或用户切换)会导致DXGI截屏失败需要可靠的检测和恢复机制。2.1 锁定状态检测优化原始代码中的IsSessionLocked检测可以进一步优化[DllImport(user32.dll)] private static extern IntPtr GetForegroundWindow(); public static bool IsActuallyLocked() { // 检测会话锁定状态 if (IsSessionLocked()) return true; // 额外检查是否有登录界面在前台 var hwnd GetForegroundWindow(); if (hwnd IntPtr.Zero) return true; // 其他自定义检测逻辑... return false; }2.2 健壮的恢复机制基于心跳检测的恢复机制可以这样实现private CancellationTokenSource _monitorCts; private async Task StartMonitorAsync() { _monitorCts new CancellationTokenSource(); while (!_monitorCts.Token.IsCancellationRequested) { await Task.Delay(1000, _monitorCts.Token); if (_lastFrameTime.AddSeconds(5) DateTime.Now) { if (!IsActuallyLocked()) { await RestartCaptureAsync(); } else { // 处理锁定状态下的资源清理 CleanupLockedResources(); } } } } private DateTime _lastFrameTime DateTime.Now; private async Task RestartCaptureAsync() { try { await StopCaptureAsync(); await Task.Delay(500); // 等待资源释放 await StartCaptureAsync(); } catch (Exception ex) { // 记录日志并尝试再次恢复 LogRecoveryError(ex); await Task.Delay(5000); await RestartCaptureAsync(); } }3. 内存与资源管理最佳实践DXGI截屏涉及非托管资源不当管理会导致内存泄漏和系统不稳定。3.1 安全的Bitmap处理模式public static void ProcessFrame(IntPtr Image, int width, int height, int RowPitch) { Bitmap bitmap null; BitmapData bitmapData default; try { bitmap new Bitmap(width, height, RowPitch, PixelFormat.Format32bppRgb, Image); bitmapData bitmap.LockBits( new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); // 处理图像数据... } finally { if (bitmapData ! default) bitmap.UnlockBits(bitmapData); bitmap?.Dispose(); // 强制GC回收防止大内存对象堆积 if (width * height 1024 * 768) GC.Collect(0, GCCollectionMode.Optimized); } }3.2 资源泄漏检测工具在开发阶段可以添加资源跟踪代码private static int _bitmapCount 0; public static void TrackBitmapCreation() { Interlocked.Increment(ref _bitmapCount); Debug.WriteLine($当前Bitmap实例数: {_bitmapCount}); } public static void TrackBitmapDisposal() { Interlocked.Decrement(ref _bitmapCount); Debug.WriteLine($当前Bitmap实例数: {_bitmapCount}); }4. 性能优化与稳定性增强长时间运行的截屏应用需要特别关注性能和稳定性。4.1 帧率控制与负载均衡private readonly Stopwatch _frameTimer new Stopwatch(); private long _targetFrameTimeMs 1000 / 30; // 30 FPS public static void CallBackFunction(IntPtr Image, int width, int height, int RowPitch, int ScreenNumber) { if (!_frameTimer.IsRunning) { _frameTimer.Start(); } else { var elapsed _frameTimer.ElapsedMilliseconds; if (elapsed _targetFrameTimeMs) { var delay (int)(_targetFrameTimeMs - elapsed); Thread.Sleep(delay); } _frameTimer.Restart(); } // 处理帧数据... }4.2 异常处理框架建立分层的异常处理机制public class DxgiCaptureSession : IDisposable { private readonly ILogger _logger; private readonly ListIDisposable _resources new ListIDisposable(); public DxgiCaptureSession(ILogger logger) { _logger logger; } public void Start() { try { RegisterCallbacks(); InitializeDxgi(); StartMonitorThread(); } catch (DllNotFoundException ex) { _logger.Error(DXGI组件缺失, ex); throw new CaptureInitializationException(DXGI组件缺失, ex); } catch (Exception ex) { _logger.Error(初始化失败, ex); throw new CaptureInitializationException(初始化失败, ex); } } private void RegisterCallbacks() { var callback new CallbackDelegate(SafeCallback); _resources.Add(new UnmanagedCallback(callback)); SetRegisterFunctionCallback(callback); } private void SafeCallback(IntPtr Image, int width, int height, int RowPitch, int ScreenNumber) { try { ProcessFrame(Image, width, height, RowPitch, ScreenNumber); } catch (Exception ex) { _logger.Error($处理屏幕{ScreenNumber}数据失败, ex); } } public void Dispose() { foreach (var resource in _resources) { try { resource.Dispose(); } catch (Exception ex) { _logger.Error(资源释放失败, ex); } } } }在实际项目中这套方案成功将DXGI截屏的稳定性从几小时提升到连续运行数周不中断。关键在于对多显示器场景的完善处理、会话锁定的智能恢复以及严格的资源管理。