上一篇文章(对WebMatrix 进行现代化改造, 使其完美支持中文)里, 我谈了如何修改WebMatrix使其支持中文. 文章末尾提到已经能正常显示选择中文了, 但时不时会弹出一个错误: 缓冲操作当前正在进行中,无法释放BufferedGraphicsContext , 如图:
我不懂这个错误是怎么发生的, 所以我在最外层的错误处理函数出加了一个断点, 结果发现每次异常发生的时候, 错误信息都不一样(汗~~~~), 有时是"参数无效", 此时调用堆栈是:
在 System.Drawing.Graphics.GetHdc()
在 Microsoft.Matrix.UIComponents.SourceEditing.TextView.MeasureString(Char[] chars, Int32 startIndex, Int32 length) 位置 D:\projects\Visual Studio Projects\VS2008\AppMatrix\Matrix.Core\UIComponents.SourceEditing\TextView.cs:行号 2116
在 Microsoft.Matrix.UIComponents.SourceEditing.TextView.GetActualIndex(TextLine line, Int32 xPos) 位置 D:\projects\Visual Studio Projects\VS2008\AppMatrix\Matrix.Core\UIComponents.SourceEditing\TextView.cs:行号 349
在 Microsoft.Matrix.UIComponents.SourceEditing.TextView.GetLineColFromXY(Int32 x, Int32 y) 位置 D:\projects\Visual Studio Projects\VS2008\AppMatrix\Matrix.Core\UIComponents.SourceEditing\TextView.cs:行号 369
在 Microsoft.Matrix.UIComponents.SourceEditing.TextView.OnMouseMove(MouseEventArgs e) 位置 D:\projects\Visual Studio Projects\VS2008\AppMatrix\Matrix.Core\UIComponents.SourceEditing\TextView.cs:行号 1749
在 System.Windows.Forms.Control.WmMouseMove(Message& m)
在 System.Windows.Forms.Control.WndProc(Message& m)
在 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
在 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
在 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
有时是"System.OutOfMemoryException: 无法创建与屏幕兼容的位图。无法确定屏幕位图格式", 调用堆栈是:
在 System.Drawing.BufferedGraphicsContext.bFillBitmapInfo(IntPtr hdc, IntPtr hpal, BITMAPINFO_FLAT& pbmi)
在 System.Drawing.BufferedGraphicsContext.CreateCompatibleDIB(IntPtr hdc, IntPtr hpal, Int32 ulWidth, Int32 ulHeight, IntPtr& ppvBits)
在 System.Drawing.BufferedGraphicsContext.CreateBuffer(IntPtr src, Int32 offsetX, Int32 offsetY, Int32 width, Int32 height)
在 System.Drawing.BufferedGraphicsContext.AllocBuffer(Graphics targetGraphics, IntPtr targetDC, Rectangle targetRectangle)
在 System.Drawing.BufferedGraphicsContext.AllocBufferInTempManager(Graphics targetGraphics, IntPtr targetDC, Rectangle targetRectangle)
在 System.Drawing.BufferedGraphicsContext.Allocate(IntPtr targetDC, Rectangle targetRectangle)
在 System.Windows.Forms.Control.WmPaint(Message& m)
在 System.Windows.Forms.Control.WndProc(Message& m)
在 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
在 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
在 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
我在网上搜了下, 有好些朋友也都碰到相同问题, 但没搜到一个解决方法. 后来到google点com搜英文资料, 搜到了一篇英文文章, 文中没有给出直接的解决方法, 但给我了一个提示, 后来根据这个提示对代码做了相应的修改后, 这个错误再没出现.
此文连接:http://nomagichere.blogspot.com/2008/03/systemcomponentmodelwin32exception-is.html (托伟大的GFW的福, 我们人人都成了爬墙专家)
文中重点内容如下:
We've been trying to track down a really sneaky problem for months. Every now and then, at seemingly random times, our application crashes.
......
One day, while trolling the bug bucket looking for something to do, I found an issue about disposing Graphics objects. That's a good thing to do, I figured, so I dug into the code and looked for CreateGraphics() calls. To my dismay, there were quite a few that were not being disposed, or were not in a using() block.
I added the calls to Dispose(), or put the objects in a using() block, and went on my way.
A couple of weeks later, one of our developers stopped by my desk to ask about a strange thing. He had been working on some automated testing of the application, and he noticed that all of a sudden, it stopped crashing. He had been getting lots of System.ComponentModel.Win32Exceptions after running his automated tests, and in the past few builds, they were gone.
......
大意是:
这几个月我们一直在查找一个非常诡异的bug....我们的应用程序时不时的崩溃, 看起来像是随性的
......
有一天, 我在bug跟踪软件浏览以便找件事情做的时候, 我发现了一个和销毁(Dispose)Graphics对象有关的问题, 我就跟进了并在代码里查找对CreateGraphics()的调用, 然后发现很多调用最后都没有销毁(Dispose)Graphics对象, 我就对这些调用进行修改, 或者增加Dispose()调用, 或者使用using代码块. 然后我就继续干别的去了.
几个星期后, 一个程序员来到我的办公桌旁, 说他发现了一件奇怪的事情: 他在进行自动化测试的时候, 发现程序很奇怪的突然再也不崩溃了. 以前他进行测试的时候, 经常会得到一堆System.ComponentModel.Win32Exceptions 异常, 但现在, 那些异常突然的就蒸发没了.
......
我对WebMatrix的改造正好是使用到了Graphics对象, 用来计算字符串的宽度, 代码如下:
private Interop.SIZE MeasureString(char[] chars, int startIndex, int length)
{
IntPtr fontPtr = this.Font.ToHfont();
Graphics graphics = Graphics.FromHwnd(IntPtr.Zero);
IntPtr hdc = graphics.GetHdc();
Interop.SelectObject(hdc, fontPtr);
Interop.SIZE size = this.MeasureString(hdc, chars, startIndex, length);
Interop.SelectObject(hdc, IntPtr.Zero);
graphics.ReleaseHdc(hdc);
graphics.Dispose();
return size;}
该函数调用频率很高, 而每次调用都会创建一个Graphics对象, 综合上面的那位国外同行的经验, 我怀疑是因为Graphics对象(或者是Hdc对象)创建数量过多, 而GC回收的速度赶不上创建的速度, 最终导致 .NET Framework 无法创建更多的Graphics对象(或者是Hdc对象)而报错. 如果真的是这样, 那么一个解决办法就是尽量少不创建新的Graphics对象(或者是Hdc对象).
为了验证, 我把该函数改成这样:
private Interop.SIZE MeasureString(char[] chars, int startIndex, int length)
{
Graphics g = this.CreateGraphics();
SizeF s = g.MeasureString(new string(chars, startIndex, length), this.Font);
g.Dispose();Interop.SIZE size = new Interop.SIZE();
size.x = (int)s.Width;
size.y = (int)s.Height;return size;
}
再编译运行, 结果不管怎么折腾, 程序再也不会出现 "缓冲操作当前正在进行中,无法释放BufferedGraphicsContext" 这个奇怪的错误.
结论:
很有可能是因为Graphics对象(或者是Hdc对象)创建数量过多, 而GC回收的速度赶不上创建的速度, 最终导致 .NET Framework 无法创建更多的Graphics对象(或者是Hdc对象)而报错.
解决办法:
Graphics对象使用完毕后就调用Dispose销毁掉, 不要全部依赖GC的回收.
PS:
由于我本人水平有限, 研究得也不是很深入, 这个结论很可能不是完全正确的. 如果结论有错, 欢迎对这方面研究得比较深入的童鞋指出. 批评永远是欢迎的, 不欢迎"正面报道为主".