You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用System.Drawing.Graphics.GetHdc()触发ArgumentException的排查求助

问题描述

我是一名技术水平有限的业余开发者,编写了一个获取屏幕像素颜色的程序,其中GetColorAtCheckPoint方法为复制所得。程序运行约20分钟内无异常,期间该方法被调用数百至数千次,但运行约20分钟后会崩溃,抛出System.ArgumentException异常,异常发生在System.Drawing.Graphics.GetHdc()方法调用处。

相关调用代码

Color colorAtCheckpoint = ColorChecker.GetColorAtCheckPoint(pos.X,pos.Y);

复制的方法代码

public static Color GetColorAtCheckPoint(int checkpointX, int checkpointY)
{
    screenPixel = new Bitmap(1, 1, PixelFormat.Format32bppArgb);
    using (Graphics gdest = Graphics.FromImage(screenPixel))
    {
        using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero))
        {
            IntPtr hSrcDC = gsrc.GetHdc();
            IntPtr hDC = gdest.GetHdc();
            int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, checkpointX, checkpointY, (int)CopyPixelOperation.SourceCopy);
            gdest.ReleaseHdc();
            gsrc.ReleaseHdc();
        }
    }
    //MessageBox.Show(new Form() { TopMost = true }, $"Color Found: " + screenPixel.GetPixel(0, 0));
    return screenPixel.GetPixel(0, 0);
}

完整错误信息1

应用程序: MyApp.exe
框架版本: v4.0.30319
描述: 进程因未处理的异常而终止。

异常信息: System.ArgumentException
   在 System.Drawing.Graphics.GetHdc()
   在 MyApp.ColorChecker.GetColorAtCheckPoint(Int32, Int32)
   在 MyApp.MyAppMain+<Run>d__4.MoveNext()
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
   在 System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   在 MyApp.MainWindow+<Start>d__15.MoveNext()
   在 System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__6_0(System.Object)
   在 System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
   在 System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
   在 System.Windows.Threading.DispatcherOperation.InvokeImpl()
   在 System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(System.Object)
   在 MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(System.Object)
   在 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   在 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   在 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
   在 MS.Internal.CulturePreservingExecutionContext.Run(MS.Internal.CulturePreservingExecutionContext, System.Threading.ContextCallback, System.Object)
   在 System.Windows.Threading.DispatcherOperation.Invoke()
   在 System.Windows.Threading.Dispatcher.ProcessQueue()
   在 System.Windows.Threading.Dispatcher.WndProcHook(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
   在 MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
   在 MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)
   在 System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
   在 System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
   在 System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Int32)
   在 MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)
   在 MS.Win32.UnsafeNativeMethods.DispatchMessage(System.Windows.Interop.MSG ByRef)
   在 System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
   在 System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)
   在 System.Windows.Application.RunDispatcher(System.Object)
   在 System.Windows.Application.RunInternal(System.Windows.Window)
   在 System.Windows.Application.Run(System.Windows.Window)
   在 System.Windows.Application.Run()
   在 MyApp.App.Main()

错误信息2

出错应用程序名称: MyApp.exe, 版本: 1.0.0.0, 时间戳: 0x84c44dec
出错模块名称: KERNELBASE.dll, 版本: 10.0.19041.3570, 时间戳: 0xfaa05682
异常代码: 0xe0434352
错误偏移量: 0x0013d982
出错进程ID: 0x4fc
出错应用程序启动时间: 0x01da07e229d3bede
出错应用程序路径: C:\Program Files\MyApp\MyApp.exe
出错模块路径: C:\Windows\System32\KERNELBASE.dll
报告ID: 099f4e2d-fce9-4d2f-8678-5a54bdffa50c
出错包完整名称: 

提问:是复制的代码存在问题,还是System.Drawing组件存在潜在bug?


问题分析与解决方案

这是复制的代码存在GDI资源泄漏问题,并非System.Drawing组件的bug。

核心问题

  1. 静态Bitmap未释放:代码中的screenPixel是静态字段,每次调用方法都会创建新的Bitmap实例,但旧实例从未被Dispose。Bitmap属于GDI资源,系统对每个进程的GDI句柄数量有限制(默认通常为10000左右),长时间调用后会耗尽所有可用句柄,导致后续GetHdc()调用失败,抛出ArgumentException。
  2. 资源释放不严谨:即使screenPixel不是静态变量,当前代码也未在使用完后释放这个Bitmap,同样会导致泄漏。

修复后的代码

将screenPixel改为局部变量并使用using语句包裹,确保每次使用后自动释放GDI资源;同时用try/finally保证句柄一定会被释放:

public static Color GetColorAtCheckPoint(int checkpointX, int checkpointY)
{
    // 使用using包裹Bitmap,确保自动释放资源
    using (var screenPixel = new Bitmap(1, 1, PixelFormat.Format32bppArgb))
    {
        using (Graphics gdest = Graphics.FromImage(screenPixel))
        {
            using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero))
            {
                IntPtr hSrcDC = gsrc.GetHdc();
                IntPtr hDC = gdest.GetHdc();
                try
                {
                    BitBlt(hDC, 0, 0, 1, 1, hSrcDC, checkpointX, checkpointY, (int)CopyPixelOperation.SourceCopy);
                }
                finally
                {
                    // 确保无论BitBlt是否成功,都释放句柄
                    gdest.ReleaseHdc();
                    gsrc.ReleaseHdc();
                }
            }
        }
        return screenPixel.GetPixel(0, 0);
    }
}

额外优化建议

  • 避免频繁创建Bitmap和Graphics实例,可以考虑复用一个全局的Bitmap(需保证线程安全,且定期释放/重置)。
  • 可以使用更高效的方式获取屏幕像素,比如直接调用GetPixel原生API,减少GDI资源的使用。

内容的提问来源于stack exchange,提问作者trading_dog

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 11:25:59