What does the CS_CLASSDC class style do? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20060602-00/?p=30993
Raymond Chen 2006年06月02日
CS_CLASSDC 类样式有什么作用?
简要
本文讨论了
CS_CLASSDC
类样式的问题,指出它会导致跨窗口和线程间共享同一个设备上下文(DC),从而引发竞态条件和难以调试的错误。作者建议现代软件不应使用此样式,它仅适用于16位Windows的单线程环境。
正文
上一次,我讨论了CS_OWNDC类样式的历史背景,以及为什么它一开始听起来是个好主意,但当你再想想时,却变成了一个可怕的想法。 CS_CLASSDC
类样式也是一样的,但更糟糕,因为它继承了CS_OWNDC
的所有问题,并将它们放大了。回想一下,CS_OWNDC
类样式指示窗口管理器为窗口创建一个DC(设备上下文),并使用这个单一的DC响应BeginPaint
和GetDC
的调用。
CS_CLASSDC
进一步发展了这一点,并为该类的所有窗口创建了一个DC。所以,上一次我展示的那个函数认为它对一个窗口有两个不同的DC的问题,现在甚至可能发生在不同窗口之间。 你以为你有一个窗口有一个DC,另一个窗口有另一个DC,但实际上它们是同一个! 更糟糕的是,两个线程可以同时使用同一个DC。GDI(图形设备接口)中没有任何东西禁止这样做;
这只是一场看哪个线程的更改占上风的比赛:“最后写入者获胜”。 想象一下,两个线程恰好各自有一个来自同一个窗口类的CS_CLASSDC
窗口,假设两个窗口都需要重绘。每个窗口都会收到一个WM_PAINT
消息,两个线程都进入它们的绘制代码。但是这些线程不知道的是,它们正在操作同一个DC。
线程A | 线程B |
---|---|
HDC hdc = BeginPaint(hwnd, &ps); | |
HDC hdc = BeginPaint(hwnd, &ps); | |
SetTextColor(hdc, red); | |
SetTextColor(hdc, blue); | |
DrawText(hdc, …); | |
DrawText(hdc, …); |
线程A期望文本是红色的,它将文本颜色设置为红色,然后绘制文本。它怎么会想到就在那一刻,线程B去把它改成了蓝色?
这是你很可能永远无法在受控条件下研究的竞态条件错误类型。你只会收到客户的bug报告,说可能每个月一次,一个项目以错误的色调出现,你可能偶尔会自己看到它,但当你设置调试器断点时,它永远不会发生。即使你添加了额外的诊断代码,你所看到的只是这个:
...
SetTextColor(hdc, red);
ASSERT(GetTextColor(hdc) == red); // 断言触发了!
DrawText(hdc, ...);
很好,断言触发了。 你刚刚设置的颜色不在那里。 现在你打算怎么办? 也许你只会说“愚蠢的有缺陷的Windows”,然后改变你的代码为
// 愚蠢的有缺陷的Windows。出于某种原因,
// 大约每个月一次,SetTextColor不起作用
// 我们必须调用它两次。
do {
SetTextColor(hdc, red);
} while (GetTextColor(hdc) != red);
DrawText(hdc, ...);
甚至这也不能解决问题,因为线程B可能在GetTextColor
和DrawText
调用之后将颜色改为蓝色。 现在,每六个月只有一次项目以错误的色调出现。
你咒骂微软,发誓从现在开始开发Mac软件。
好吧,所以现在我希望我说服了你,CS_CLASSDC
是一个可怕的想法。 但如果它如此根本上存在缺陷,为什么它最初会存在呢?
因为16位Windows是合作式多任务的。 在16位世界中,你不必担心另一个线程潜入并搞乱你的DC,因为,正如我已经指出的,你正在运行意味着没有其他人正在运行。这种多线程灾难场景根本不可能发生,所以CS_CLASSDC
只是比CS_OWNDC
稍微不那么疯狂一点。 引入具有单个进程中多个线程的抢占式多任务处理,是将我们带入了“这根本没有机会正常工作”的世界。 这个类样式的存在是为了那些在16位代码中使用它的人可以移植到Win32(只要他们承诺保持单线程应用程序),但没有任何现代软件应该使用它。