调用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。
核心问题
- 静态Bitmap未释放:代码中的
screenPixel是静态字段,每次调用方法都会创建新的Bitmap实例,但旧实例从未被Dispose。Bitmap属于GDI资源,系统对每个进程的GDI句柄数量有限制(默认通常为10000左右),长时间调用后会耗尽所有可用句柄,导致后续GetHdc()调用失败,抛出ArgumentException。 - 资源释放不严谨:即使
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
相关产品推荐
相关产品推荐

