在上一篇文章中,我们了解了 CS_OWNDC 标志位的历史,也说明了设计它的初衷。
这个标志位一开始看起来是个挺好的设计,但是如果你多琢磨一会儿,就会发现它不是一个好主意。今天我们来看看更糟的。
CS_CLASSDC 标志位有点类似 CS_OWNDC ,但更糟糕的是,它把 CS_OWNDC 的所有问题都放大了。此话怎讲?
我们先回想一下,CS_OWNDC 标志指示窗口管理器为窗口创建 DC,并使用该单个 DC 来响应对 BeginPaint 和 GetDC 的调用。CS_CLASSDC 更进一步,为该类的所有窗口创建一个 DC。因此,我上次使用一个函数显示的问题,该函数认为它有一个窗口有两个不同的 DC,现在甚至可以跨窗口发声。你认为一个窗口有一个 DC,另一个窗口有另一个 DC,但实际上它们是相同的!
更糟糕的是,两个线程可以同时使用相同的 DC。GDI 中没有任何禁止它的内容;这只是一场竞赛,看看哪个线程的变化占上风:”最后一个界面绘制代码将会获胜”。
假设两个线程碰巧每个线程都有一个来自同一窗口类的 CS_CLASSDC 窗口,并假设两个窗口都需要重新绘制。每个窗口都会收到一条 WM_PAINT 消息,两个线程都进入其绘制代码。
但这些线程不知道的是它们在同一个 DC 上运行。
>> 请移步至 topomel.com 以查看图片 <<
在线程 A 中运行的代码完全期望文本为红色,因为它将文本颜色设置为红色,然后绘制文本。怎么知道就在那一刻,线程 B 去把它改成了蓝色?
这是一种竞争条件错误,你可能永远无法在受控条件下研究。你只会收到来自客户的错误报告,说也许每个月一次,一个项目的颜色错误,也许你自己偶尔会看到它,但当你设置了调试器断点时,它永远不会发生。即使添加其他诊断代码,也只会看到以下内容:
>> 请移步至 topomel.com 以查看图片 <<
太好了,断言被触发了。你刚刚设置的颜色不存在。现在你要做什么?也许你只会说“这不会是操作系统出了Bug吧?”并将你的代码更改为下图的代码:
>> 请移步至 topomel.com 以查看图片 <<
即使这样也不能解决问题,因为线程 B 可能在 GetTextColor 和调用 DrawText 后将颜色更改为蓝色。现在,每六个月只有一次,绘制的的颜色是错误的。
你向 Microsoft 发誓 ,发誓从现在开始开发 MAC 平台上的软件。
好的,所以现在我希望我已经说服你,CS_CLASSDC 是一个可怕的坏主意。但是,如果它有如此根本性的缺陷,为什么它会被设计出来?
因为 16 位 Windows 是协作式多任务的。在 16 位世界中,你不必担心另一个线程潜入并弄乱你的 DC,因为正如我已经指出的,你正在运行的事实意味着没有其他人在运行。这整个多线程灾难场景不会发生,因此 CS_CLASSDC 仅比 CS_OWNDC 稍微古怪。
在单个进程中引入具有多个线程的先发制人的多任务处理将我们带入了“这没有机会正常工作”的世界(这段话有点长,再读一次)。类样式的存在使得在 16 位代码中使用它的人可以移植到 Win32(只要他们承诺保持单线程应用程序),但现代软件不应该使用它。
总结
今天的总结是:CS_CLASSDC 咱哥几个是碰都不要碰,谁碰谁知道。
最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《What does the CS_CLASSDC class style do?》