В прошлый раз я говорил об исторической подоплёке стиля оконных классов
CS_OWNDC
и почему изначально это звучит как отличная идея, но после близкого знакомства оказывается, что это больше похоже на ужасную идею.Стиль
CS_CLASSDC
- это то же самое, только хуже. Потому что он берёт все проблемы CS_OWNDC
и умножает их.Вспомните, что стиль
CS_OWNDC
указывает оконному менеджеру создать DC для окна и использовать его всегда в любых вызовах BeginPaint
и GetDC
. Стиль CS_CLASSDC
выводит это поведение на новый уровень и создаёт один DC для всех окон этого класса. Так что проблема, на которую я указал в последний раз с функцией, думающей что она работает с двумя разными DC, теперь может произойти не только для одного окна и двух DC, но и для двух окон и по DC на каждое. Вы думаете, что у вас на руках есть один DC для одного окна и другой DC - для второго, но на самом деле это один и тот же DC!Что делает это ещё хуже: теперь два потока могут использовать один и тот же DC одновременно. В GDI не предусмотрено никаких механизмов по блокировке использования DC из нескольких потоков; тут будет просто условия гонки: кто последним произвёл запись - тот и выиграл. Представьте себе два потока, которым передали два окна одного класса со стилем
CS_CLASSDC
, и предположим, что оба окна нуждаются в перерисовке. Тогда каждое окно получит сообщение WM_PAINT, а оба потока начнут выполнение их кода рисования. Но что эти потоки не знают - так это то, что они оба работают с одним DC.Поток A | Поток B |
---|---|
hdc := BeginPaint(hwnd, ps); | |
hdc := BeginPaint(hwnd, ps); | |
SetTextColor(hdc, red); | |
SetTextColor(hdc, blue); | |
DrawText(hdc, ...); | |
DrawText(hdc, ...); |
Код, работающий в потоке A имеет полное право ожидать, что он рисует красный текст - потому что, в конце концов, он явно установил цвет шрифта в красный, а затем начал рисовать. Как он может узнать, что именно в этот момент процессор переключился на поток B, который изменил шрифт на синий?
Это пример бага, который вы, вероятно, никогда не сможете воспроизвести у себя в контролируемом окружении. Вы просто будете получать баг-отчёты от клиентов, в которых написано, что где-то раз в месяц текст в программе показывается другим цветом - и даже, быть может, вы сами видите это поведение раз в месяц - но оно никогда не произойдёт, когда вы запускаете отладчик с установленными точками остановка. Даже если вы добавите дополнительный отладочный код - всё что вы увидите:
... SetTextColor(hdc, red); Assert(GetTextColor(hdc) = red); // срабатывает утверждение! DrawText(hdc, ...);Класс, сработало утверждение. Цвет, который вы только что установили, почему-то стал другим. Что теперь собираетесь делать? Возможно вы скажете "Тупая глючная Винда" и измените код на:
// Тупая глючная винда. // Почему-то раз в месяц SetTextColor не срабатывает и приходится вызывать её дважды repeat SetTextColor(hdc, red); until (GetTextColor(hdc) = red); DrawText(hdc, ...);Но даже это не решит проблемы, потому что поток B может изменить цвет сразу после проверки с
GetTextColor
, но до вызова DrawText
. В итоге, баг теперь появляется не раз в месяц, а раз в шесть месяцев.Пожалуй, вы проклянёте Microsoft и поклянётесь отныне программировать только под Mac.
Okей, надеюсь, что к этому моменту я убедил вас, что
CS_CLASSDC
- это ужасно-ужасно плохая идея. Но возникает вопрос: раз уж этот стиль имеет такой фундаментальный изъян - почему он вообще существует?Потому что 16-битные Windows применяли кооперативную многозадачность! В 16-битном мире вы не волновались о том, что кто-то угонит DC для своих операций прямо у вас под носом - потому что, как мы уже говорили, тот факт, что сейчас работаете вы, означал, что никто другой работать не может - что означает, что никто другой не может работать с вашим DC, пока вы явно не отдадите процессор. Вся эта многопоточная катастрофа просто не могла произойти в древнем мире, так что
CS_CLASSDC
был не хуже CS_OWNDC
. Введение вытесняющей многозадачности с несколькими потоками в одном процессе - вот что привело нас к "да чтобы это работало правильно? Без шансов!". Эти классовые стили продолжают существовать, чтобы люди могли портировать их 16-битный код в Win32 (это будет работать, пока они обещают оставить их старые приложения однопоточными), но ни один современный код не должен их использовать.
Про мьютексы или критические секции - не не слышал ;)
ОтветитьУдалить