分类:
2009-10-25 15:05:14
上回我讲到 CS_OWNDC
这个 class style 的历史背景,以及为什么一开始大家都觉得这主意不错,但再转几念一想就觉得是个坏主意的原因。
CS_CLASSDC
这个 class style 不但一样,而且更糟。它不但继承了 CS_OWNDC
的带来的所有问题,而且还将其发扬光大。回想一下,CS_OWNDC
这个 class style 让窗口管理器为该窗口创建一个 DC 并在对 BeginPaint
和 GetDC
的调用作出反应时使用该 DC。CS_CLASSDC
则更进一步,为属于该 class 的所有窗口创建同一个 DC。于是,上次我展示的那个由一个自认为对同一个窗口拥有两个的 DCs 的函数所引发的问题现在终于可以跨窗口地发作了。你觉得一个窗口有一个 DC,而另一个窗口有另一个 DC,可事实是它们是同一个东西。
线程甲 | 线程乙 |
---|---|
HDC hdc = BeginPaint(hwnd, &ps); |
|
HDC hdc = BeginPaint(hwnd, &ps); | |
SetTextColor(hdc, red); |
|
SetTextColor(hdc, blue); | |
DrawText(hdc, ...); |
|
DrawText(hdc, ...); |
线程甲里的代码认为文本应该是红的,因为它是先把文本颜色设为红色再把文本画出来的。而它又怎么知道,就在这时,线程乙跑了过来把颜色改成了蓝的?
这就是某种竞态条件 bug,受控条件下几乎查不出来。你只会从客户那儿收到 bug 报告说大概每隔一个月就会出一回颜色问题,抑或你自己也会隔三差五地发现,可你调试器设上断点后就是没问题。就算你加上诊断代码也只会看见:
SetTextColor(hdc, red);
ASSERT(GetTextColor(hdc) == red); // 触发断言!
DrawText(hdc, ...);
很好,断言被触发了。才设置的颜色没了。现在准备干什么?可能你只会说“漏洞百出的 Windows”,然后把代码换成
// 漏洞百出的 Windows. 介于某些原因,
// 大约每隔一个月 SetTextColor 就失灵一回
// 于是我们不得不调用两回.
do {
SetTextColor(hdc, red);
} while (GetTextColor(hdc) != red);
DrawText(hdc, ...);
而即便如此也还没解决问题,因为线程乙可能在 GetTextColor
之后把颜色改蓝了,然后 DrawText
再被调用。于是乎,这回要每隔半年才会出颜色问题了。
继而你大骂微软,发誓说我从此就去开发 Mac 软件了。
OK ,希望你同意我 CS_CLASSDC
是个可怕的坏主意的想法。可如果它天生就有这缺陷,为什么打一开始就有它呢?
原来,16 位 Windows 是合作型多任务的。在 16 位世界里,你不必担心另一个线程偷偷潜进来把你的 DC 搞得一团糟,原因之前我也说过,即“你在运行就是别人不在运行”的道理。这整个多线程灾难场景发生不了,于是 CS_CLASSDC
只是小小地比 CS_OWNDC
怪异了点。而一进程多线程的抢先式多线程的引入才是真正带领我们走进“没法儿把这东西搞正常了”新时代的领路人。这样一个 class style 存在着,于是人们可以把在 16 位代码移植到 Win32 上(只要它们还是单线程应用程序),但现代软件还是不要用的好。