суббота, 13 августа 2011 г.

Что делает стиль оконного класса CS_CLASSDC?

Это перевод What does the CS_CLASSDC class style do? Автор: Реймонд Чен.

В прошлый раз я говорил об исторической подоплёке стиля оконных классов 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 (это будет работать, пока они обещают оставить их старые приложения однопоточными), но ни один современный код не должен их использовать.

1 комментарий:

  1. Про мьютексы или критические секции - не не слышал ;)

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.