GDI+的雷区
2005-4-22
千呼万唤之中,微软终于推出了早该有的精心设计的下一代图形程序接口GDI+,并成为dotNet的图形绘制核心,这些也可以算是陈年旧事了。当几批给微软趟雷的勇士倒下后,大家开始发现GDI+实为鸡肋,食之无味,弃之可惜。
雷区:贼慢的速度
诸位应该也有很多人都已经注意到了,不是一般的慢。找微软和MVP给的优化方法都没用,根本原因有两个,一个是GDI+目前不使用硬件加速,准确的说它基于GDI的部分是通过GDI加速的,其他的部分是纯CPU计算;二是算法实现有问题,如典型的画位图的问题,绘制的速度和源位图大小有关,注意不是和实际绘制的部分的大小有关,而是和源位图的总大小有关。就是说绘制128x128的位图和绘制1024x1024的位图的一个128x128的部分,这两种方法都只画128x128,但速度有天壤之别,这个问题在好多论坛都讨论过,大家都不能理解不能接受,只好去用GDI。不要以为这是没有使用MSDN和那些MVP的优化建议,不相信的可以自己试试。
雷区:失败的封装
dotNet最好的地方是提供了一套设计优秀、风格统一的API,但GDI+的封装部分,就是System.Drawing名字空间里的东西,却做得很失败。dotNet的核心优势是垃圾收集,对纯内存数据对象,是不需要操心其生存期管理的,不但简化了内存管理,而且使必须为函数的返回值分配内存的糟糕的API接口成为过去,可以既提供直观易用又没有性能损失的函数。按着垃圾收集模式的精神,是应该鼓励不需要Dispose的对象的。但Sytem.Drawing名字空间里的类几乎全部需要Dispose,连Matrix和StringFormat这种东西都要Dispose。纠其原因,就是这些类没有提供dotNet的实现,而是包装的GDI+对象的指针,这种方式节省了他们开发的时间,却给了我们无尽的麻烦,而且处于兼容性考虑,以后的版本也很难将那个IDisposable接口从那些类上去掉。
而更糟的是微软的样例代码里都不掉用Dispose,这样代码看起来很清晰简洁,却要一群MVP四处劝说一定要Dispose。知道了它的内部实现,每个程序员都会认为它确实应该被尽快Dispose,而不能等垃圾收集去做,因为垃圾收集器是不知道这些非托管资源占了多少内存、占了多少系统资源的。写dotNet控件的程序员经常遇到IDE变慢甚至报告资源不足的情况,就是有太多的GDI+对象没释放的关系。而MSDN可没有教人Dispose啊,连Bitmap对象都没!
知道了应该Dispose,那么就Dispose呗。
Pen blackPen = new Pen(Color.Black, 3);
e.Graphics.DrawLine(blackPen, x1, y1, x2, y2);
blackPen.Dispose();
这行了吧,不行!DrawLine可能抛异常啊,那样blackPen还是没有释放,所以要
Pen blackPen = null;
Try {
blackPen = new Pen(Color.Black, 3);
e.Graphics.DrawLine(blackPen, x1, y1, x2, y2);
}
finally {
blackPen.Dispose();
}
幸亏C#里提供了uisng,可以简化为
using (blackPen = new Pen(Color.Black, 3)) {
e.Graphics.DrawLine(blackPen, x1, y1, x2, y2);
}
可是一个GDI+对象还好,10个怎么办,在绘图程序里可不止这几个啊,难道using (…) using (…) using (…) using (…) …??这代码还能读吗?
于是大家也只能破罐破摔,小东西就不管了。
雷区3:和交互效率不高
由于速度问题,大家只能求救于GDI,想那GDI+提供了不少和GDI交互的函数,就像早就知道大家有这么一天似的。可一用就不对了,因为大部分情况下它是拷贝一份生成GDI对象,GDI操作完再拷贝回来。真是让人预哭无泪。
雷区4:又提供新的基于加速的图形,还要做什么?
正如Joel所说,微软现在不停的出各种各样的新的API,又在不久之后做出新的替代品而放弃旧的,对程序员来说就是恶梦。旧问题不解决,而用新的替代,然后新的又带来一堆新问题,然后再出更新的来替代。如果不是必要,不要在GDI+上下太大的赌注。
结论
当然,天下没有完美的东西,虽然有很多缺陷,GDI+的功能还是非常有吸引力,要求不高的话还是很有用的。这里给大家提供了GDI+的几大雷区,希望大家不要都在趟雷中牺牲了。