• Отладка
  • Замечания о Visual C++ 
  • Советы и рекомендации 
  • Приложение А. Информация для разработчиков

    Вот и все — книга подходит к концу. Однако наше внимание было настолько приковано к DirectDraw (и DirectInput), что некоторые важные темы так и не были рассмотрены. Например, в DirectDraw есть несколько досадных недочетов, о которых необходимо знать (если вы еще не успели столкнуться с ними). Свои недостатки есть и у Visual C++. Кроме того, некоторые особенности программного кода на CD-ROM могут представлять для вас интерес. Во время работы над программами я отобрал ряд полезных советов. Наконец, у меня есть несколько общих замечаний, которые не удалось привязать к основному тексту. Все эти темы рассматриваются в приложении.

    Начнем с разговора об отладке. Конкретнее, мы изучим несколько способов отладки полноэкранных приложений DirectDraw (иногда это превращается в сплошные мучения). После этого мы поговорим о Visual C++, а также об ошибках и раздражающих недостатках DirectDraw — если не знать о них, ваш прогресс может надолго остановиться.

    Отладка

    Говорят, некоторым программистам нравится отлаживать свои программы — я не отношусь к их числу. Приятно узнать, почему ваша программа постоянно «зависает» или почему спрайт неправильно выводится на экран — но я охотно поменял бы это чувство удовлетворения на те напрасно потраченные часы и дни, когда я скрежетал зубами, заново компилировал свои программы и в сотый раз перезагружал компьютер.

    Конечно, наша рабочая среда не идеальна, а инструменты еще не прошли всего пути развития. Всю программную отрасль постоянно лихорадит от багов. Они приводят к задержкам, сворачиванию и отмене крупных и мелких проектов. На искоренение особо зловредных багов истрачены многие миллионы долларов.

    Но, работая над проектом, содержащим множество багов, вы теряете не только деньги — вы теряете чувство уверенности. Программирование — дело непростое, и не стоит усложнять его попытками внести новые возможности в еще не отлаженную программу. Мы, программисты, видели достаточно багов и привыкли к ним, но такое положение дел никак нельзя считать нормальным.

    Иногда ошибки возникают по вине Windows, иногда — по вине DirectX или какой-нибудь библиотеки классов, но подавляющее большинство багов лежит на совести прикладных программистов. Если вы нашли ошибку в программе, лучше бросить все дела и заняться ее искоренением. Не стоит усложнять ситуацию и продолжать работу, зная, что в вашей программе прячутся баги.

    Об ошибках и способах отладки написаны многие тома. Особенно ценной я считаю книгу «Writing Solid Code» Стива Магуайра (Steve Maguire) (Microsoft Press, ISBN 1-55615-551-4). Если вы не читали ее ранее, обязательно прочтите. В этом приложении мы ограничимся проблемами и способами отладки, которые относятся к полноэкранным приложениям DirectDraw.


    Проблемы

    Отладить полноэкранное приложение намного сложнее, чем любое другое, и для этого есть несколько причин. Самая очевидная из них — раз приложение занимает весь экран, вы не сможете изменить точки прерывания, просмотреть код программы или значения переменных. Дело усложняется переключением страниц. Если программа должна вывести проверочное сообщение или отладочное окно, нельзя гарантировать, что оно действительно появится на экране, потому что Windows может вывести информацию во вторичном буфере. И даже если вам повезет и вы сможете увидеть это окно (при использовании буферизации ваши шансы — 50 на 50), вероятно, его все равно не удастся толком разглядеть из-за различий между текущей и системной палитрой.

    Следовательно, многие традиционные механизмы и методики отладки не работают для DirectDraw. Интегрированный отладчик Visual C++ практически бесполезен, а пользоваться стандартными отладочными макросами типа ASSERT() или VERIFY() оказывается рискованно.


    Windows NT и Windows 95

    Первый шаг в великой битве с багами — переход на Windows NT (если вы еще не успели этого сделать). Разумеется, вам не придется отказываться от поддержки Windows 95, но для разработки NT подходит лучше, а приложения DirectX переносятся из NT в 95 без перекомпиляции. Просто Windows NT работает более устойчиво, чем Windows 95. NT редко (очень редко) требует перезагрузки, тогда как в 95 это вполне обычное явление.

    Оборотная сторона заключается в том, что по возможностям DirectX NT часто отстает от 95. Поскольку в Windows NT нельзя установить стандартную runtime-часть DirectX, на поддержку новых возможностей у Microsoft уходит больше времени. Следовательно, если вам необходимы новейшие возможности DirectX, то для разработки неизбежно придется использовать Windows 95. Для DirectDraw это не вызывает особых проблем, потому что в последних версиях эта библиотека практически не изменялась. А вот другие DirectX API, напротив, заметно изменились в DirectX 5 (особенно это относится к DirectInput и DirectPlay).

    Профессиональные разработчики для DirectX отдают явное предпочтение NT. Более того, некоторые будущие бета-версии DirectX могут предназначаться только для NT.


    Отладочные макросы

    Visual C++ (как и многие другие среды разработки) содержит макросы, предназначенные специально для отладки. Например, макросы TRACE(), ASSERT() и VERIFY() приносят огромную пользу в процессе разработки. Они предназначены для разных целей, но у всех трех макросов есть нечто общее — они не отягощают итоговый выполняемый файл. При построении отладочных версий вашей программы макросы ведут себя так, как им положено. Однако в «окончательной» версии они удаляются из программы (вместе с теми возможностями, которые ими обеспечивались).

    Макрос TRACE() посылает диагностические сообщения в окно отладчика. Правильно составленные сообщения образуют протокол событий, который можно просмотреть после выполнения программы (а средства удаленной отладки, о которой мы вскоре поговорим, позволяют сделать это даже во время работы программы). Макросы TRACE(), содержащие коды возврата и описания ошибок, помогают отыскать источники багов в ваших программах. Макрос TRACE() в отличие от двух других нормально работает в полноэкранных приложениях DirectDraw, так что вы можете свободно пользоваться им (этот макрос регулярно встречается в программах на CD-ROM).

    Макросы ASSERT() и VERIFY() очень похожи друг на друга, что часто приводит к недоразумениям. На самом деле между ними существует очень важное различие. В отладочных версиях макросы ASSERT() и VERIFY() работают одинаково. Оба макроса вычисляют выражение, переданное им в качестве аргумента, и прекращают работу программы с выводом сообщения в том случае, если это выражение оказывается равным нулю. Отличие заключается в том, что в окончательной версии макрос ASSERT() вместе с вычисляемым выражением полностью удаляется из кода. С другой стороны, выражение макроса VERIFY() остается в программе. Макрос ASSERT() предназначен для проверки состояния переменных, а VERIFY() — для проверки функций. Выражение макроса ASSERT() относится только к состоянию переменных и не является обязательной частью программы, поэтому его можно удалить из окончательной версии.

    Использование макросов ASSERT() и VERIFY() в полноэкранных приложениях DirectDraw осложняется тем, что при неудачной проверке выводится диалоговое окно. Несовместимость палитры может привести к искажению окна, а из-за переключения страниц окно может и вовсе не появиться на экране.

    При таких затруднениях у вас есть два варианта: отказаться от ASSERT() и VERIFY() или предоставить нестандартные версии, работающие в DirectDraw. Второй вариант предпочтительнее, и, как выясняется, он реализуется достаточно просто.

    Если покопаться в заголовочных файлах MFC, вы увидите, что в отладочном режиме макрос ASSERT() определяется так:

    #define ASSERT(f) \

     do \

     { \

     if (!(f) && AfxAssertFailedLine(THIS_FILE, __LINE__)) \

      AfxDebugBreak(); \

     } while (0) \

    Выглядит довольно странно. Но вместо того, чтобы пытаться расшифровать логику его работы, мы согласимся с тем, что макрос работает, и попытаемся изменить его так, чтобы он правильно работал в приложениях DirectDraw. Однако перед этим следует заметить, что вывод диалогового окна и завершение приложения выполняются с помощью вызова AfxAssertFailedLine(). Следовательно, любой код, добавленный в этот условный оператор (что на первый взгляд кажется логичным), выполняться не будет.

    Теперь давайте подумаем, что нужно сделать для нормального отображения диалогового окна. Можно вызвать функцию DirectDraw FlipToGDISurface() и обеспечить вывод диалогового окна на первичной поверхности, но проблема с палитрой при этом остается, к тому же окно может быть выведено в неверном видеорежиме. Вместо этого мы воспользуемся функцией RestoreDisplayMode() — это гарантирует вывод диалогового окна, активизацию стандартной палитры Windows и возврат к исходному видеорежиму Windows. Видоизмененный код выглядит так:

    #define ASSERT(f) \

     do \

     { \

      if (!(f)) \

      { \

       if (GetDDWin()) \

       { \

        GetDDWin()->GetDDraw()->RestoreDisplayMode(); \

        GetDDWin()->GetDDraw()->Release(); \

       } \

       AfxAssertFailedLine(THIS_FILE, __LINE__); \

       AfxDebugBreak(); \

      } \

     } while (0) \

    Для работы с объектом DirectDraw применяются функции GetDDWin() и GetDDraw() (которые соответственно возвращают указатели на объект DirectDrawWin и интерфейс DirectDraw). Помимо вызова функции RestoreDisplayMode() мы для приличия освобождаем объект DirectDraw. Также обратите внимание на перестановку, в результате которой наш код будет выполняться перед вызовом функции AfxAssertFailedLine().

    Перейдем к макросу VERIFY(). Возвращаясь к заголовочным файлам MFC, мы находим, что в отладочной версии VERIFY() реализуется с помощью макроса ASSERT():

    #define VERIFY(f)   ASSERT(f)

    Вспомните — в отладочной версии ASSERT() и VERIFY() ведут себя одинаково. Раз ASSERT() и VERIFY() реализуются одним макросом, VERIFY() можно оставить без изменений. Сказанное относится и к окончательным версиям макросов ASSERT() и VERIFY(), потому что нам не потребуется изменять их поведение. При компиляции окончательной версии ASSERT() и VERIFY() определяются так:

    #define ASSERT(f)   ((void)0)

    #define VERIFY(f)   ((void)(f))

    Как было сказано выше, выражение, передаваемое макросу ASSERT(), удаляется из окончательной версии (в действительности оно заменяется выражением ((void)0), которое игнорируется компилятором). Выражения, передаваемые VERIFY(), остаются в коде программы, однако их значение больше не проверяется.

    Нам остается лишь переопределить стандартный вариант ASSERT() из MFC своим нестандартным вариантом. Для этого необходимо сначала отменить определение макроса из MFC. Кроме того, нужно позаботиться о том, чтобы подстановка осуществлялась только в отладочной версии. Окончательный код выглядит так:

    #ifdef _DEBUG#undef ASSERT#define ASSERT(f) \

     do \

     { \

      if (!(f)) \

      { \

       if (GetDDWin()) \

       { \

        GetDDWin()->GetDDraw()->RestoreDisplayMode(); \

        GetDDWin()->GetDDraw()->Release(); \

       } \

       AfxAssertFailedLine(THIS_FILE, __LINE__); \

       AfxDebugBreak(); \

      } \

     } while (0) \

    #endif _DEBUG

    Модифицированный макрос находится на CD-ROM и готов к работе, поэтому если вы захотите внести изменения в какую-нибудь программу, то можете свободно пользоваться макросами TRACE(), ASSERT() и VERIFY(). Кроме того, этот код автоматически генерируется и включается в проекты, создаваемые DirectDraw AppWizard.


    Удаленная отладка

    Процесс отладки упрощается, если вам повезло и в вашем распоряжении оказались два компьютера, объединенных в локальную сеть. В этом случае возможна удаленная отладка, при которой на одном компьютере работает отлаживаемая программа, а на другом — отладчик Visual C++. При удаленной отладке исчезают все проблемы, связанные с переключением страниц, палитрами и отображением отладчика. Отладчик всегда присутствует на экране и с ним можно работать, потому что отладчик и приложение работают на разных компьютерах. Даже если ваша программа «сломается», ее состояние можно будет просмотреть в отладчике. Поскольку удаленная отладка обладает такими преимуществами (и при этом так плохо документирована), мы поговорим о том, как происходит ее настройка. Если эта тема вас не интересует, можете пропустить этот раздел.

    Для выполнения удаленной отладки вам понадобятся два компьютера, объединенных в локальную сеть. Конкретные параметры сети (количество узлов, тип кабеля, сетевые адаптеры и т. д.) не имеют значения; главное — чтобы на обоих компьютерах был установлен сетевой протокол TCP/IP. Кроме того, необходимо установить на обоих компьютерах Visual C++.

    Затем вы должны выбрать, на каком компьютере будет работать отладчик Visual C++, а на каком — приложение. Мы будем называть компьютер, на котором работает Visual C++, хостовым компьютером (или хостом), а компьютер с отлаживаемым приложением — удаленным компьютером. Обычно хостом назначают более мощный из двух компьютеров.

    Вам необходимо узнать адреса TCP/IP для обоих компьютеров. Проще всего получить этот адрес с помощью утилиты Winipcfg (WINIPCFG.EXE). Для Winipcfg не существует ярлыка (shortcut) или команды меню Start, поэтому придется либо создать ярлык или команду (утилита находится в каталоге Windows), либо запустить Winipcfg из окна DOS или диалогового окна Run меню Start. Окно утилиты Winipcfg изображено на рис. А.1. В поле IP Address приведено значение адреса TCP/IP . В нашем примере удаленный компьютер имеет адрес 128.128.128.1 (а хостовый компьютер будет иметь адрес 128.128.128.2).

    Рис. А.1. Утилита Winipcfg


    Также вам понадобится сетевое имя удаленного компьютера. Сетевое имя представляет собой неформальную строку, которая облегчает идентификацию сетевых компьютеров (при этом вам не придется запоминать адреса TCP/IP для всех компьютеров сети). Чтобы узнать сетевое имя компьютера, можно щелкнуть правой кнопкой мыши на значке Network Neighborhood (он находится на рабочем столе) и выбрать из контекстного меню команду Properties; откроется диалоговое окно Network. Перейдите на вкладку Identification. На рис. А.2 показано, как выглядит эта вкладка. В данном примере имя удаленного компьютера — remotemachine. Вероятно, вам не стоит менять сетевое имя, если только вы не являетесь сетевым администратором — просто воспользуйтесь именем, которое было назначено компьютеру при исходной настройке.

    Затем вам понадобится каталог на удаленном компьютере, где будет храниться и работать ваше приложение. Вы можете присвоить ему любое имя и разместить в любом месте жесткого диска, но на практике оказывается удобнее разместить этот каталог поближе к корневому. Вам не придется копировать выполняемый файл приложения (это будет сделано автоматически), однако следует перенести все файлы данных, необходимые для его работы. Собственно, выполняемый файл можно перенести хотя бы с целью тестирования. Чтобы отладка прошла нормально, запущенное приложение должно найти все свои файлы данных (не считая случаев, когда вы отлаживаете поведение программы при отсутствующих файлах данных).

    Рис. А.2. Диалоговое окно Network Identification


    После того как вы создадите каталог для приложения и убедитесь в том, что оно может выполняться на удаленном компьютере, запустите отладочный монитор Visual C++ (MSVCMON.EXE). Для этой программы, как и для утилиты Winipcfg, не существует ярлыка, поэтому вам придется создать его самостоятельно. Отладочный монитор находится в каталоге bin Visual C++. Если вы собираетесь плотно заняться удаленной отладкой, вероятно, ярлык отладочного монитора следует поместить в удобное место. Окно отладочного монитора Visual C++ изображено на рис. А.3.

    Теперь нужно указать адрес хостового компьютера. Убедитесь, что в отладочном мониторе выбрано соединение TCP/IP, и нажмите кнопку Settings — откроется диалоговое окно Win32 Network (TCP/IP) Settings. Введите адрес хоста в первом поле. На рис. А.4 показано, как выглядит диалоговое окно с введенным адресом.

    Вводить пароль необязательно — я рекомендую оставить это поле пустым. После ввода адреса хоста нажмите кнопку OK. Вы возвратитесь к диалоговому окну Debug Monitor. Нажмите кнопку Connect. Появится маленькое окно, которое показывает, что отладочный монитор готов начать сеанс отладки. Теперь удаленный компьютер настроен для проведения отладки.

    Рис. А.3. Диалоговое окно Visual C++ Debug Monitor

    Рис. А.4. Диалоговое окно Network Settings


    Теперь займемся настройкой хоста. Запустите Visual C++ и загрузите проект, который вы собираетесь отлаживать. В нашем примере используется проект с именем Sample. Выполните команду Project|Settings и перейдите на вкладку General. На ней задается каталог, в котором генерируется итоговый выполняемый файл. Для упрощения отладки мы будем генерировать его прямо на удаленном компьютере — это избавит нас от необходимости заново копировать выполняемый файл на удаленный компьютер после очередной компиляции. В поле Output files введите имя каталога, созданного ранее на удаленном компьютере. Имя начинается с условного обозначения сетевого файла (\\). На рис. А.5 показано, как выглядит поле Output files с указанным каталогом.

    Задаваемая строка начинается с имени удаленного компьютера (remotemachine), за которым следует имя диска (в нашем примере каталог удаленной отладки находится на диске C) и имя каталога. Перейдите на вкладку Debug; здесь необходимо изменить все три поля. Вы должны задать местонахождение целевого выполняемого файла и рабочий каталог. На рис. А.6 показано, как заполняются поля вкладки Debug в нашем примере. Как и на рис. А.5, путь к выполняемому файлу включает имя удаленного компьютера.

    Перед тем как приступать к удаленной отладке, остается сделать лишь один шаг. Выполните команду Debugger Remote Connection из меню Build. Эта команда открывает окно (похожее на окно отладочного монитора), предназначенное для активизации и настройки сеанса удаленной отладки. По умолчанию в списке Connection выбирается строка Local. Чтобы перейти к удаленной отладке, выберите строку Network (TCP/IP), как показано на рис. А.7.

    Рис. А.5. Поле Output files подготовлено для генерации выполняемого файла на удаленном компьютере

    Рис. А.6. Изменения на вкладке Debug

    Рис. А.7. Окно Remote Connection


    Нажмите кнопку Settings — появится окно, изображенное на рис. А.4. Задайте в нем адрес TCP/IP удаленного компьютера. Поле Debug monitor password оставьте пустым, если только вы не задали пароль при настройке отладочного монитора на удаленном компьютере.

    Теперь можно запускать отладчик. К сожалению, настройка еще не закончена, потому что отладчик потребует задать местонахождение всех DLL, используемых удаленным приложением. К счастью, это необходимо проделать лишь один раз.

    Запустите отладчик. Если все параметры были настроены правильно, окно Debug Monitor на удаленном компьютере исчезнет — это говорит о том, что соединение установлено.

    Удаленный компьютер попытается запустить программу, и вам будет предложено (на хостовом компьютере) указать местонахождение необходимых DLL. Для каждой DLL открывается окно Find Local Module. Вы должны ввести имя файла, включая сетевое имя компьютера. Окно Find Local Module изображено на рис. А.8 (в нем предлагается задать местонахождение файла WINMM.DLL). Часть введенной строки не поместилась на рисунке, но вы должны помнить о том, что кроме пути необходимо указать имя файла.

    Рис. А.8. Окно Find Local Module


    После того как вы укажете местонахождение всех DLL (как правило, они располагаются в каталоге windows\system), программа запускается на удаленном компьютере, однако ее отладочный вывод направляется в окно отладчика на хосте. Теперь вы можете отлаживать свою программу. Мы настроили Visual C++ так, что при каждой компиляции новый выполняемый файл будет автоматически копироваться на удаленный компьютер. Удаленная отладка почти ничем не отличается от обычной, разве что программа выполняется не на хосте, а на другом компьютере.

    Напоследок я скажу еще несколько слов об удаленной отладке. Во-первых, как ее отключить? Хороший вопрос. Выполните команду Build|Debugger Remote Connection в Visual C++ (на хосте), затем выберите строку Local в списке Connection и нажмите кнопку OK.

    Удаленная отладка обладает многими достоинствами, но она не идеальна. Прежде всего, точки прерывания в ней работают ненадежно, а то и вовсе не работают. Другая проблема заключается в том, что после настройки удаленной отладки и выбора местонахождения DLL, необходимых для вашей программы, Visual C++ будет настаивать на загрузке DLL даже после возврата в режим локальной отладки. Следовательно, в конце раздела, посвященного удаленной отладке, мне следует рассказать о том, как вернуться к нормальной загрузке DLL.

    Выполните команду Project|Settings и перейдите на вкладку Debug. Выберите из списка Category строку Additional DLL. Открывается список DLL, которые Visual C++ пытается загрузить при отладке вашей программы (независимо от того, включена удаленная отладка или нет). Чтобы отказаться от переназначения DLL (для нормальной отладки вам не потребуется ни один из элементов этого списка), выделите соответствующую строку и снимите флажок в левом верхнем углу. Список Additional DLLs с некоторыми DLL, необходимыми для типичных приложений MFC, изображен на рис. А.9.

    Рис. А.9. Окно Additional DLLs



    Отладочные сообщения DirectX

    Вероятно, вы уже заметили, что при запуске приложений DirectX в окне вывода отладчика Visual C++ появляются диагностические сообщения. По умолчанию отладочные версии компонентов DirectX сообщают таким образом о важных внутренних событиях — например, об ошибках. Типичное окно вывода для приложения DirectDraw изображено на рис. А.10.

    СОВЕТ

    Прислушивайтесь к крикам DirectX

    При установке runtime-части DirectX на рабочий компьютер возникает искушение установить окончательные версии DLL вместо отладочных (во время инсталляции вам предлагается выбрать нужный вариант). Не поддавайтесь соблазну и не устанавливайте окончательные версии! Да, они действительно работают чуть быстрее, и именно с ними будут работать пользователи вашего приложения, но при этом вы лишитесь отладочного вывода. Если во время работы над программой возникнут проблемы, вы так и не узнаете, что же DirectX пытается вам сказать.

    Рис. А.10. Типичное окно вывода в отладчике


    Возможно, вы не знаете, что детальность этих сообщений тоже можно изменять. В каждой из библиотек DirectDraw, Direct3D и DirectSound можно выбрать пять разных уровней отладочных сообщений. Чтобы настроить уровень отладочных сообщений, выберите значок DirectX в Control Panel и перейдите на вкладку нужного компонента DirectX. Диалоговое окно DirectX Properties с выбранной вкладкой DirectDraw изображено на рис. А.11.

    Рис. А.11. Окно DirectX Properties (запускается из Control Panel)


    Для библиотеки DirectDraw нажмите кнопку Advanced Settings — откроется окно DirectDraw Advanced Settings. Нужный уровень отладочных сообщений устанавливается с помощью слайдера Debug Level. Окно DirectDraw Advanced Settings изображено на рис. А.12.

    Рис. А.12. Окно DirectDraw Advanced Settings


    При максимальном уровне отладки каждое приложение DirectDraw, запущенное в отладчике, выдает весьма обширный и подробный протокол. Вывод протокола снижает быстродействие программы, так что максимальный уровень не стоит держать включенным постоянно. С другой стороны, он сильно помогает в затяжной борьбе с DirectDraw. Чтобы показать, насколько подробная информация выдается при максимальном уровне отладки, я приведу отладочный протокол для небольшого полноэкранного приложения DirectDraw. Обратите внимание на то, что листинг раскрывает некоторые внутренние тонкости работы DirectDraw. Подробный отладочный протокол приведен в листинге А.1.


    Листинг А.1. Подробный отладочный протокол DirectDraw

    DDraw:====> ENTER: DLLMAIN(baaa12c0): Process Attach: fff00c89, tid=fff04bf1

    DDraw:Thunk connects

    DDraw:Signalling DDHELP that a new process has connected

    DDraw:====> EXIT: DLLMAIN(baaa12c0): Process Attach: fff00c89

    DDraw:createDC(R3D)

    DDraw:Enumerating GUID aba52f41-f744-11cf-b4-52-00-00-1d-1b-41-26

    DDraw: Driver Name = R3D

    DDraw: Description = Righteous 3D DirectX II Driver

    DDraw:DeleteDC 0x179e

    DDraw:createDC(mm3dfx)

    DDraw:Enumerating GUID 3a0cfd01-9320-11cf-ac-a1-00-a0-24-13-c2-e2

    DDraw: Driver Name = mm3dfx

    DDraw: Description = 3Dfx Interactive DirectX Driver

    DDraw:DeleteDC 0x179e

    DDraw:Only one Display device in the current system.

    DDraw:DirectDrawCreate entered

    DDraw: GUID *:00000000, LPLPDD:0064f870, pUnkOuter:00000000

    DDraw:Registry already scanned, not doing it again

    DDraw:full name = C:\SAMPLE\DEBUG\SAMPLE.EXE

    DDraw:name = SAMPLE.EXE

    DDraw:DirectDrawCreate: pid = fff00c89

    DDraw:Reading Registry

    DDraw: ModeXOnly: 0

    DDraw: EmulationOnly: 0

    DDraw: ShowFrameRate: 0

    DDraw: EnablePrintScreen: 0

    DDraw: DisableMMX: 0

    DDraw: DisableWiderSurfaces:0

    DDraw: DisableNoSysLock:0

    DDraw: ForceNoSysLock:0

    DDraw:Signalling DDHELP to create a new DC

    DDraw:createDC(display)

    DDraw:DIRECTDRAW driver is wrong version, got 0x5250, expected 0x0100

    DDraw:getDisplayMode:

    DDraw: bpp=8, refresh=0

    DDraw: dwHeight=600, dwWidth=800

    DDraw: lStride=0

    DDraw:Driver says nummodes=9

    DDraw:Enum Display Settings says nummodes=9

    DDraw:dwModeIndex = 1

    DDraw:Masks for current mode are: 00000000 00000000 00000000

    DDraw:DirectDrawObjectCreate: oldpdd == 00000000, reset=0

    DDraw:DIRECTDRAW object passed in = 00000000

    DDraw:oldpdd == 00000000, reset=0

    DDraw:Driver Object: 2256 base bytes

    DDraw:dwReserved3 of DDrawGbl is set to 0x0

    DDraw:oldpdd == NULL || reset

    DDraw:Driver can't blt

    DDraw:pddd->lp16DD = 40cf0000

    DDraw:Adding ModeX mode 320x200x8 (standard VGA flag is 0)

    DDraw:Adding ModeX mode 320x240x8 (standard VGA flag is 0)

    DDraw:Adding ModeX mode 320x200x8 (standard VGA flag is 1)

    DDraw:All video memory heaps have been disabled. OS has no AGP support

    DDraw:Current and Original Mode = 1

    DDraw:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ MODE INDEX = 1

    DDraw:DDHALInfo contains D3D pointers: 00000000 00000000

    DDraw:createDC(display)

    DDraw:NOT Setting DDCAPS_BANKSWITCHED

    DDraw:DeleteDC 0x179e

    DDraw:Taking the Win16 lock may not be necessary for VRAM locks

    DDraw:DirectDrawObjectCreate: Returning global object 82dc11f8

    DDraw:createDC(display)

    DDraw:createDC(display)

    DDraw:DeleteDC 0x175e

    DDraw:DeleteDC 0x179e

    DDraw:Primary's rect is 0, 0, 800, 600

    DDraw:HELInit for DISPLAY Driver: Reference Count = 1

    DDraw:createDC(display)

    DDraw:DeleteDC 0x179e

    DDraw:createDC(display)

    DDraw:createDC(display)

    DDraw:DeleteDC 0x175e

    DDraw:DeleteDC 0x179e

    DDraw:***New local allocated 82dc1b1c for global pdrv 82dc11f8

    DDraw:New driver object created, interface ptr = 82dc1b84

    DDraw: DirectDrawCreate succeeds, and returns ddraw pointer 82dc1b84

    DDraw:New driver interface created, 82dc1bb0

    DDraw:DD_AddRef, pid=fff00c89, obj=82dc1bb0

    DDraw:DD_AddRef, Reference Count: Global = 2 Local = 2 Int = 1

    DDraw:DD_Release, pid=fff00c89, obj=82dc1b84

    DDraw:DD_Release, Ref Count: Global = 1 Local = 1 Interface = 0

    DDraw:*********** ALLOWING MODE X AND VGA MODES

    DDraw:DD_GetDeviceRect: display [0 0 800 600]

    DDraw:Subclassing window 00000aac

    DDraw:StartExclusiveMode

    DDraw:******** invalidating all surfaces

    DDraw:Enumerating mode 0. 640x480

    DDraw:Enumerating mode 1. 800x600

    DDraw:Enumerating mode 2. 1024x768

    DDraw:Enumerating mode 3. 1280x1024

    DDraw:Enumerating mode 4. 640x480

    DDraw:Enumerating mode 5. 800x600

    DDraw:Enumerating mode 6. 1024x768

    DDraw:Enumerating mode 7. 640x480

    DDraw:Enumerating mode 8. 800x600

    DDraw:Enumerating mode 9. 320x200

    DDraw:Enumerating mode 10. 320x240

    DDraw:Enumerating mode 11. 320x200

    DDraw:Looking for 640x480x8

    DDraw:Found 640x480x8x (flags = 1)

    DDraw:Found 800x600x8x (flags = 1)

    DDraw:Found 1024x768x8x (flags = 1)

    DDraw:Found 1280x1024x8x (flags = 1)

    DDraw:Found 640x480x16x (flags = 0)

    DDraw:Found 800x600x16x (flags = 0)

    DDraw:Found 1024x768x16x (flags = 0)

    DDraw:Found 640x480x32x (flags = 0)

    DDraw:Found 800x600x32x (flags = 0)

    DDraw:Found 320x200x8x (flags = 3)

    DDraw:Found 320x240x8x (flags = 3)

    DDraw:Found 320x200x8x (flags = 11)

    DDraw:Calling HEL SetMode

    DDraw:width = 640

    DDraw:height = 480

    DDraw:bpp = 8

    DDraw:WM_DISPLAYCHANGE: 640x480x8

    DDraw:DD_GetDeviceRect: display [0 0 640 480]

    DDraw:WM_SIZE hWnd=AAC wp=0000, lp=01E00280

    DDraw:WM_SIZE: Window restored, NOT sending WM_ACTIVATEAPP

    DDraw:createDC(display)

    DDraw:DeleteDC 0x1712

    DDraw:createDC(display)

    DDraw:createDC(display)

    DDraw:DeleteDC 0x179e

    DDraw:DeleteDC 0x1712

    DDraw:createDC(display)

    DDraw:getDisplayMode:

    DDraw: bpp=8, refresh=0

    DDraw: dwHeight=480, dwWidth=640

    DDraw: lStride=0

    DDraw:Driver says nummodes=9

    DDraw:Enum Display Settings says nummodes=9

    DDraw:dwModeIndex = 0

    DDraw:Masks for current mode are: 00000000 00000000 00000000

    DDraw:DirectDrawObjectCreate: oldpdd == 82dc11f8, reset=1

    DDraw:DIRECTDRAW object passed in = 82dc11f8

    DDraw:oldpdd == 82dc11f8, reset=1

    DDraw:Driver Object: 2256 base bytes

    DDraw:dwReserved3 of DDrawGbl is set to 0x0

    DDraw:oldpdd == NULL || reset

    DDraw:Driver can't blt

    DDraw:******** invalidating all surfaces

    DDraw:All video memory heaps have been disabled. OS has no AGP support

    DDraw:Current and Original Mode = 0

    DDraw:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ MODE INDEX = 0

    DDraw:DDHALInfo contains D3D pointers: 00000000 00000000

    DDraw:createDC(display)

    DDraw:NOT Setting DDCAPS_BANKSWITCHED

    DDraw:DeleteDC 0x179e

    DDraw:Taking the Win16 lock may not be necessary for VRAM locks

    DDraw:DirectDrawObjectCreate: Returning global object 82dc11f8

    DDraw:createDC(display)

    DDraw:createDC(display)

    DDraw:DeleteDC 0x1776

    DDraw:DeleteDC 0x179e

    DDraw:Primary's rect is 0, 0, 640, 480

    DDraw:DeleteDC 0x1712

    DDraw:Looking for 640x480x16

    DDraw:Found 640x480x8x (flags = 1)

    DDraw:Found 800x600x8x (flags = 1)

    DDraw:Found 1024x768x8x (flags = 1)

    DDraw:Found 1280x1024x8x (flags = 1)

    DDraw:Found 640x480x16x (flags = 0)

    DDraw:Found 800x600x16x (flags = 0)

    DDraw:Found 1024x768x16x (flags = 0)

    DDraw:Found 640x480x32x (flags = 0)

    DDraw:Found 800x600x32x (flags = 0)

    DDraw:Found 320x200x8x (flags = 3)

    DDraw:Found 320x240x8x (flags = 3)

    DDraw:Found 320x200x8x (flags = 11)

    DDraw:Calling HEL SetMode

    DDraw:width = 640

    DDraw:height = 480

    DDraw:bpp = 16

    DDraw:Window 00000ac4 is on top of us!!

    DDraw:Window 00000ac4 is on top of us!!

    DDraw:Window 00000ac4 is on top of us!!

    DDraw:WM_DISPLAYCHANGE: 640x480x16

    DDraw:DD_GetDeviceRect: display [0 0 640 480]

    DDraw:createDC(display)

    DDraw:DeleteDC 0x172a

    DDraw:createDC(display)

    DDraw:createDC(display)

    DDraw:DeleteDC 0xc96

    DDraw:DeleteDC 0x172a

    DDraw:createDC(display)

    DDraw:getDisplayMode:

    DDraw: bpp=16, refresh=0

    DDraw: dwHeight=480, dwWidth=640

    DDraw: lStride=0

    DDraw:Driver says nummodes=9

    DDraw:Enum Display Settings says nummodes=9

    DDraw:dwModeIndex = 4

    DDraw:Masks for current mode are: 00007c00 000003e0 0000001f

    DDraw:DirectDrawObjectCreate: oldpdd == 82dc11f8, reset=1

    DDraw:DIRECTDRAW object passed in = 82dc11f8

    DDraw:oldpdd == 82dc11f8, reset=1

    DDraw:Driver Object: 2256 base bytes

    DDraw:dwReserved3 of DDrawGbl is set to 0x0

    DDraw:oldpdd == NULL || reset

    DDraw:Driver can't blt

    DDraw:******** invalidating all surfaces

    DDraw:All video memory heaps have been disabled. OS has no AGP support

    DDraw:Current and Original Mode = 4

    DDraw:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ MODE INDEX = 4

    DDraw:DDHALInfo contains D3D pointers: 00000000 00000000

    DDraw:createDC(display)

    DDraw:NOT Setting DDCAPS_BANKSWITCHED

    DDraw:DeleteDC 0xc96

    DDraw:Taking the Win16 lock may not be necessary for VRAM locks

    DDraw:DirectDrawObjectCreate: Returning global object 82dc11f8

    DDraw:createDC(display)

    DDraw:createDC(display)

    DDraw:DeleteDC 0x179e

    DDraw:DeleteDC 0xc96

    DDraw:Primary's rect is 0, 0, 640, 480

    DDraw:DeleteDC 0x172a

    DDraw:82dc1bb0->CreateSurface

    DDraw: DDSURFACEDESC->dwBackBufferCount = 1

    DDraw: DDSURFACEDESC->lpSurface = 00000000

    DDraw: DDSCAPS_COMPLEX

    DDraw: DDSCAPS_FLIP

    DDraw: DDSCAPS_PRIMARYSURFACE

    DDraw:******** invalidating all primary surfaces

    DDraw:#### Using GDI screen bpp = 16

    DDraw:*** allocating primary surface

    DDraw:createDC(display)

    DDraw:createDC(display)

    DDraw:DeleteDC 0x179e

    DDraw:DeleteDC 0xc96

    DDraw:#### Using GDI screen bpp = 16

    DDraw:*** allocating a backbuffer

    DDraw:HEL:About to allocate 614400 bytes for the surface

    DDraw:DD_Surface_AddRef, Reference Count: Global = 1 Local = 1 Int = 1

    DDraw:DD_Surface_AddRef, Reference Count: Global = 1 Local = 1 Int = 1

    DDraw: CreateSurface returns 00000000 (0)

    DDraw:DD_Surface_AddRef, Reference Count: Global = 2 Local = 2 Int = 2

    DDraw:82dc1bb0->CreateSurface

    DDraw: DDSURFACEDESC->dwHeight = 240

    DDraw: DDSURFACEDESC->dwWidth = 320

    DDraw: DDSURFACEDESC->lpSurface = 00000000

    DDraw: DDSCAPS_OFFSCREENPLAIN

    DDraw: DDSCAPS_VIDEOMEMORY

    DDraw:No hardware support

    DDraw: CreateSurface returns 88760233 (563)

    DDraw:82dc1bb0->CreateSurface

    DDraw: DDSURFACEDESC->dwHeight = 240

    DDraw: DDSURFACEDESC->dwWidth = 320

    DDraw: DDSURFACEDESC->lpSurface = 00000000

    DDraw: DDSCAPS_OFFSCREENPLAIN

    DDraw: DDSCAPS_SYSTEMMEMORY

    DDraw:Forcing pixel format for explicit system memory surface

    DDraw:#### Got surface pixel format bpp = 16

    DDraw:*** allocating a surface 320x240x16

    DDraw:HEL: About to allocate 153600 bytes for the surface of
    640x240 with alignemnt 8

    DDraw:DD_Surface_AddRef, Reference Count: Global = 1 Local = 1 Int = 1

    DDraw: CreateSurface returns 00000000 (0)

    DDraw:82dc1f74->Lock

    DDraw: DDSURFACEDESC->dwHeight = 240

    DDraw: DDSURFACEDESC->dwWidth = 320

    DDraw: DDSURFACEDESC->lPitch = 640

    DDraw: DDSURFACEDESC->lpSurface = 004b7118

    DDraw:Flags:

    DDraw: DDPF_RGB

    DDraw: BitCount:16

    DDraw: Bitmasks: R/Y:00007c00, G/U:000003e0, B/V:0000001f, Alpha/Z:00000000

    DDraw: DDSCAPS_OFFSCREENPLAIN

    DDraw: DDSCAPS_SYSTEMMEMORY

    DDraw:WM_SIZE hWnd=AAC wp=0000, lp=01E00280

    DDraw:WM_SIZE: Window restored, sending WM_ACTIVATEAPP

    DDraw:WM_ACTIVATEAPP: BEGIN Activating app pid=fff00c89, tid=fff04bf1

    DDraw:*** Already activated

    DDraw:WM_ACTIVATEAPP: DONE Activating app pid=fff00c89, tid=fff04bf1

    DDraw:Bringing window to top

    DDraw:WM_ACTIVATEAPP: BEGIN Deactivating app pid=fff00c89, tid=fff04bf1

    DDraw:*** Active state changing

    DDraw:******** invalidating all surfaces

    DDraw:INACTIVE: fff00c89: Restoring original mode (1)

    DDraw:In RestoreDisplayMode

    DDraw:Turning off DCI in mySetMode

    DDraw:WM_DISPLAYCHANGE: 800x600x8

    DDraw:DD_GetDeviceRect: display [0 0 800 600]

    DDraw:WM_SIZE hWnd=AAC wp=0000, lp=02580320

    DDraw:WM_SIZE: Window restored, NOT sending WM_ACTIVATEAPP

    DDraw:WM_ACTIVATEAPP: BEGIN Deactivating app pid=fff00c89, tid=fff04bf1

    DDraw:*** Already deactivated

    DDraw:WM_ACTIVATEAPP: DONE Deactivating app pid=fff00c89, tid=fff04bf1

    DDraw:createDC(display)

    DDraw:createDC(display)

    DDraw:DeleteDC 0x159a

    DDraw:DeleteDC 0xa4a

    DDraw:RestoreDisplayMode: Process fff00c89 Mode = 4

    DDraw:createDC(display)

    DDraw:getDisplayMode:

    DDraw: bpp=8, refresh=0

    DDraw: dwHeight=600, dwWidth=800

    DDraw: lStride=0

    DDraw:Driver says nummodes=9

    DDraw:Enum Display Settings says nummodes=9

    DDraw:dwModeIndex = 1

    DDraw:Masks for current mode are: 00000000 00000000 00000000

    DDraw:DirectDrawObjectCreate: oldpdd == 82dc11f8, reset=1

    DDraw:DIRECTDRAW object passed in = 82dc11f8

    DDraw:oldpdd == 82dc11f8, reset=1

    DDraw:Driver Object: 2256 base bytes

    DDraw:dwReserved3 of DDrawGbl is set to 0x0

    DDraw:oldpdd == NULL || reset

    DDraw:Driver can't blt

    DDraw:******** invalidating all surfaces

    DDraw:All video memory heaps have been disabled. OS has no AGP support

    DDraw:Current and Original Mode = 1

    DDraw:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ MODE INDEX = 1

    DDraw:DDHALInfo contains D3D pointers: 00000000 00000000

    DDraw:createDC(display)

    DDraw:NOT Setting DDCAPS_BANKSWITCHED

    DDraw:DeleteDC 0x170a

    DDraw:Taking the Win16 lock may not be necessary for VRAM locks

    DDraw:DirectDrawObjectCreate: Returning global object 82dc11f8

    DDraw:createDC(display)

    DDraw:createDC(display)

    DDraw:DeleteDC 0xaa2

    DDraw:DeleteDC 0x170a

    DDraw:Primary's rect is 0, 0, 800, 600

    DDraw:DeleteDC 0x13ba

    DDraw:Redrawing all windows

    DDraw:DoneExclusiveMode

    DDraw:Enabling error mode, hotkeys

    DDraw:Mode was never changed by this app

    DDraw:WM_ACTIVATEAPP: DONE Deactivating app pid=fff00c89, tid=fff04bf1

    DDraw:DD_Surface_Release, Reference Count: Global = 0 Local = 0 Int = 0

    DDraw:Deleting attachment from 82dc1e98 to 82dc1b84 (implicit = 1)

    DDraw:DeleteOneAttachment: 82dc1b84,82dc1e98

    DDraw:DD_Surface_AddRef, Reference Count: Global = 3 Local = 3 Int = 3

    DDraw:Leaving AddRef early to prevent recursion

    DDraw:DeleteOneLink: 82dc1e98,82dc1b84

    DDraw:DeleteOneLink: 82dc1b84,82dc1e98

    DDraw:DD_Surface_Release, Reference Count: Global = 2 Local = 2 Int = 2

    DDraw:Leaving Release early to prevent recursion

    DDraw:DD_Surface_Release, Reference Count: Global = 1 Local = 1 Int = 1

    DDraw:DD_Surface_Release, Reference Count: Global = 0 Local = 0 Int = 0

    DDraw:Freeing pointer 0042110c

    DDraw:DD_Release, pid=fff00c89, obj=82dc1bb0

    DDraw:DD_Release, Ref Count: Global = 0 Local = 0 Interface = 0

    DDraw:Unsubclassing window 00000aac

    DDraw:ProcessSurfaceCleanup

    DDraw:Process fff00c89 had 1 accesses to surface 82dc1f74

    DDraw:DD_Surface_Release, Reference Count: Global = 0 Local = 0 Int = 0

    DDraw:Freeing pointer 004b7118

    DDraw:Leaving ProcessSurfaceCleanup

    DDraw:ProcessPaletteCleanup, ppal=00000000

    DDraw:ProcessClipperCleanup

    DDraw:Cleaning up clippers owned by driver object 0x82dc11f8

    DDraw:Not cleaning up clippers not owned by a driver object

    DDraw:ProcessVideoPortCleanup

    DDraw:Leaving ProcessVideoPortCleanup

    DDraw:Mode was never changed by this app

    DDraw:FREEING DRIVER OBJECT

    DDraw:Calling HEL DestroyDriver

    DDraw:HEL DestroyDriver: dwHELRefCnt=0

    DDraw:3 surfaces allocated - 768000 bytes total

    DDraw:*********** DDHEL TIMING INFO ************

    DDraw:myFlip: 30 calls, 1.365 sec (0.045)

    DDraw:myLock: 1 calls, 0.000 sec (0.000)

    DDraw:myUnlock: 1 calls, 0.000 sec (0.000)

    DDraw:******************************************

    DDraw:Frequency(cycles/second)(0) 1193180

    DDraw:Blt16_SrcCopy(32): SUM = 264860

    DDraw:Blt16_SrcCopy(32): COUNT = 30

    DDraw:Blt16_SrcCopy(32): AVG = 8828

    DDraw:Blt16_SrcCopy(32): MIN = 8572

    DDraw:Blt16_SrcCopy(32): MAX = 9964

    DDraw:Blt16_SrcCopy(32): Dst MB/sec = 17

    DDraw:Blt16_ColorFill(47): SUM = 716435

    DDraw:Blt16_ColorFill(47): COUNT = 32

    DDraw:Blt16_ColorFill(47): AVG = 22388

    DDraw:Blt16_ColorFill(47): MIN = 15855

    DDraw:Blt16_ColorFill(47): MAX = 55858

    DDraw:Blt16_ColorFill(47): Dst MB/sec = 27

    DDraw:P6SrcCopy(Bypass Blt16_SrcCopy)(88): SUM = 263495

    DDraw:P6SrcCopy(Bypass Blt16_SrcCopy)(88): COUNT = 30

    DDraw:P6SrcCopy(Bypass Blt16_SrcCopy)(88): AVG = 8783

    DDraw:P6SrcCopy(Bypass Blt16_SrcCopy)(88): MIN = 8528

    DDraw:P6SrcCopy(Bypass Blt16_SrcCopy)(88): MAX = 9917

    DDraw:P6SrcCopy(Bypass Blt16_SrcCopy)(88): Dst MB/sec = 17

    DDraw:Driver is now FREE

    DDraw:====> ENTER: DLLMAIN(baaa12c0): Process Detach fff00c89,
    tid=fff04bf1

    DDraw:MemState

    DDraw:Memory still allocated! Alloc count = 11

    DDraw:Current Process (pid) = fff00c89

    DDraw:82dc100c: dwSize=00000008, lpAddr=baaa1bbc (pid=fff1b349)

    DDraw:82dc1054: dwSize=0000001d, lpAddr=baae5093 (pid=fff18e61)

    DDraw:82dc1090: dwSize=00000019, lpAddr=baae5093 (pid=fff18e61)

    DDraw:82dc10c8: dwSize=00000019, lpAddr=baae5093 (pid=fff18e61)

    DDraw:82dc1100: dwSize=0000001d, lpAddr=baae5093 (pid=fff18e61)

    DDraw:82dc113c: dwSize=00000018, lpAddr=baae5093 (pid=fff18e61)

    DDraw:82dc1170: dwSize=00000019, lpAddr=baae5093 (pid=fff18e61)

    DDraw:82dc11a8: dwSize=0000001c, lpAddr=baae52c8 (pid=fff18e61)

    DDraw:82dc1030: dwSize=00000008, lpAddr=baaa1bbc (pid=fff04a0d)

    DDraw:82dc1fc0: dwSize=00000008, lpAddr=baaa1bbc (pid=fff04a0d)

    DDraw:82dc1c3c: dwSize=00000008, lpAddr=baaa1bbc (pid=fff00c89)

    DDraw:Total Memory Unfreed From Current Process = 8 bytes

    DDraw:====> EXIT: DLLMAIN(baaa12c0): Process Detach fff00c89

    Замечания о Visual C++ 

    Как бы вы ни относились к Visual C++, приходится признать: это чрезвычайно мощный пакет. В нем объединены компилятор, компоновщик, отладчик, профайлер и редактор ресурсов — я перечислил лишь основные компоненты. С каждой новой версией он становится все больше и мощнее. Однако сказанное относилось только к C++! Сам по себе Visual C++ является интегрированным компонентом Developer Studio — многоцелевой и многоязыковой платформы разработчика, с которой мне никогда не надоедает работать.

    Visual C++ тоже не стоит на месте. Хорошо это или плохо, но в него постоянно вносятся изменения, а значит — возникают новые проблемы. К тому же Microsoft иногда стремится опередить события и под давлением маркетинговых и финансовых соображений выпускает продукты, которым полагалось бы находиться на стадии бета-тестирования. Трудно сказать, относится ли сказанное к Visual C++ 5.0. Впрочем, на момент написания книги уже существовало дополнение (service pack), исправляющее целый ряд багов. Чаще всего от ошибок страдают новые средства Visual C++ (например, говорят, что их хватает в новой библиотеке ActiveX Template Library (ATL)). К счастью, в данной книге эти новые возможности не используются.

    И все же рано или поздно встреча с новшествами состоится. Одна из новых возможностей, с которыми вам неизбежно придется столкнуться, — справочная система на базе HTML. Я не понимаю, чем она лучше предыдущей. Более того, она медленнее работает, обладает меньшей маневренностью, и в довершение всех бед новые справочные файлы написаны еще хуже прежних. Правда, их можно просматривать в броузере, но я не понимаю, зачем это нужно. (Вообще, все эти нововведения начинают сильно смахивать на попытку использовать компьютер без клавиатуры).

    С другой стороны, в Visual C++ появилось несколько хороших, хотя и запоздалых возможностей. Один из примеров — новые ключевые слова bool, true и false. В течение некоторого времени они уже входили в стандарт C++; хорошо, что мы наконец сможем ими пользоваться.

    В этом разделе мы поговорим о Visual C++. Одни вопросы относятся к программам из этой книги, другие — ко всем проектам, написанным на Visual C++.

    Прекомпилированные заголовки 

    Лично я — горячий поклонник прекомпилированных заголовков. Настроить их нетрудно, а компиляция проектов Visual C++ проходит намного быстрее обычного. Если вам придется компилировать большой проект без прекомпилированных заголовков, то к концу компиляции вы успеете забыть, что же именно изменилось в вашей программе.

    Одна из разрекламированных особенностей Visual C++, которую я ждал с особым нетерпением, — возможность задания параметров проекта из пользовательских AppWizard. Это позволило бы мне написать AppWizard, который помимо генерации исходных текстов мог бы настраивать конфигурацию новых проектов. Особенно сильно я рассчитывал на настройку и включение прекомпилированных заголовков.

    Такая возможность была предусмотрена для стандартного MFC AppWizard из комплекта Visual C++; впрочем, она присутствовала и в предыдущих версиях. После нововведений в Visual C++ 5.0 то же самое могут (теоретически) сделать и разработчики нестандартных AppWizard.

    Первая проблема состоит в том, что справка, документирующая эту новую возможность, была написана для Visual Basic и потому оказалась практически бесполезной для программистов на C++ (несомненно, мы имеем дело с побочным эффектом многоязыковой ориентации Developer Studio). Вторая проблема — в том, что (в соответствии с документацией) уровень доступа, необходимый для оптимальной конфигурации прекомпилированных заголовков, вам не предоставляется. Выяснилось, что с проектами можно работать на уровне конфигураций, но не на уровне файлов. До меня доходили слухи, что такая возможность все-таки есть, но она не документирована; однако мои поиски в заголовочных файлах ни к чему не привели.

    Все проекты, созданные на базе прилагаемого к этой книге AppWizard, обладают одним общим недостатком — отсутствием полноценной поддержки прекомпилированных заголовков (точнее, это относится не к программам на CD-ROM, а к новым проектам, сгенерированным в DirectDraw AppWizard). Все, что я могу вам посоветовать, — включить эту возможность после создания нового проекта (сделать это несложно, но все-таки лучше, если бы это происходило автоматически). Сейчас я расскажу о том, как настроить прекомпилированные заголовки для только что созданного проекта с именем Sample.

    Рис. А.13. Вкладка C/C++ окна Project Settings


    После того, как вы создадите новый проект с помощью DirectDraw AppWizard, выполните команду Project|Settings и перейдите на вкладку C/C++. Затем выберите из списка Category строку Precompiled Headers. По умолчанию устанавливается переключатель Automatic use of Precompiled Headers, однако автоматическая настройка прекомпилированных заголовков намного уступает ручной. Стандартный вид вкладки C/C++ окна Project Settings изображен на рис. А.13.

    Выберите из расположенного слева списка Setting For строку All Configurations. Благодаря этому мы сможем разрешить применение прекомпилированных заголовков как в окончательной, так и в отладочной конфигурации. Теперь установите переключатель Use precompiled header file и введите в поле Through header строку headers.h — имя файла, в котором будут храниться данные прекомпилированных заголовков.

    Теперь раскройте в иерархическом дереве слева узел Sample — появятся три узла следующего уровня. Раскройте узел Source Files. Выберите файл Headers.cpp, один раз щелкнув на его строке. Затем установите переключатель Create precompiled header file и введите в поле Through header строку headers.h. Диалоговое окно после внесения всех необходимых изменений изображено на рис. А.14.

    Рис. А.14. Вкладка C/C++ окна Project Settings после внесения изменений


    Наконец, нажмите кнопку OK и откомпилируйте проект. Обратите внимание на то, что сначала компилируется файл Headers.cpp. На этой стадии Visual C++ создает прекомпилированный заголовочный файл, что требует некоторого времени. Однако последующие модули компилируются быстрее, потому что заголовочные файлы уже были откомпилированы ранее. Также обратите внимание на то, что файл Headers.cpp почти не приходится компилировать, потому что он содержит только часто используемые, но редко изменяемые заголовочные файлы. Если теперь внести изменения в другие файлы проекта, они будут компилироваться быстрее.

    И последнее замечание. Лучшими кандидатами для прекомпиляции являются заголовочные файлы MFC и DirectX (именно они включены в файл Headers.h во всех проектах на CD-ROM, а также в тех, что создаются DirectDraw AppWizard). Тем не менее, если эти файлы изменятся (например, из-за выхода новой версии Visual C++ или DirectX), вы должны обновить прекомпилированные заголовки командой Rebuild All.

    Файлы DirectX SDK 

    Начиная с версии 4.2, Visual C++ содержит DirectX SDK. К сожалению, обычно в Visual C++ входит относительно старая версия SDK (Visual C++ 4.2 поставляется с DirectX 2 SDK, а Visual C++ 5.0 — с DirectX 3 SDK). Скорее всего, из-за этого вы будете работать с версией DirectX SDK, полученной в другом месте (например, на Web-узле Microsoft).

    Тем не менее при установке новой версии SDK часто возникают проблемы, потому что по умолчанию старые файлы DirectX не замещаются новыми. Это происходит из-за того, что Microsoft помещает файлы DirectX в стандартные каталоги include и lib (если файлы MFC хранятся в отдельных каталогах, почему Microsoft не могла сделать того же с файлами DirectX?).

    Если вы столкнетесь с этой проблемой (это произойдет, когда программа, использующая новые возможности DirectX, откажется компилироваться), у вас есть два варианта:

    • скопировать новые файлы DirectX SDK поверх старых;

    • сделать так, чтобы новые каталоги SDK рассматривались раньше старых.

    Первый вариант нежелателен, потому что стандартные файлы Visual C++ обычно не стоит изменять. Кроме того, вам придется заново копировать файлы с выходом каждой новой версии Visual C++ или DirectX SDK.

    Лучше воспользоваться вторым вариантом. Для этого выполните команду Tools | Options и перейдите на вкладку Directories. Затем выберите каталог с файлами DirectX SDK и поместите его в списке над другими каталогами (с помощью кнопки­). На рис. А.15 показано, как может выглядеть список каталогов после внесения необходимых изменений.

    На рисунке изображены каталоги, которые Visual C++ просматривает в поисках включаемых файлов (список каталогов просматриваются сверху вниз). Кроме того, не забудьте аналогичным образом изменить порядок каталогов для библиотечных (LIB) файлов.

    Рис. А.15. Вкладка Directories окна Options 


    Фокусы ClassView 

    ClassView — древовидный элемент окна рабочей области, в котором отображается список классов, входящих в проект (он достаточно удобен для перемещения по проекту). Узлы дерева соответствуют классам; раскрывая их, вы можете просмотреть члены класса. Хотя на первый взгляд ClassView Visual C++ 5.0 ничем не отличается от предыдущих версий, на самом деле он ведет себя несколько иначе.

    В Visual C++ 5.0 ClassView отображает лишь те классы, чьи заголовочные файлы были явно включены в проект (командой Project|Add to Project|Files). Если в проект не включено ни одного H-файла, в ClassView не будет ни одного класса. Этим он отличается от Visual C++ 4.x, где выводились все классы из файлов проекта и тех файлов, от которых зависят файлы проекта. Это может показаться шагом назад, но на самом деле это новое поведение позволяет управлять составом классов, отображаемых в ClassView (в предыдущих версиях такого выбора не было).

    При импортировании старых проектов в Visual C++ 5.0 по умолчанию отображаются все классы. Класс отсутствует в ClassView лишь в том случае, если CPP-файл был включен в проект без соответствующего H-файла.

     Работа с Visual C++ 4.0 

    Хотя Visual C++ 4.0 не используется в этой книге напрямую, ничто не помешает вам работать с ним. Но так как на CD-ROM находятся файлы проектов только для Visual C++ 5.0 (для которого обратная совместимость не предусмотрена), вам придется создать файлы рабочей области самостоятельно. Это делается так:

    1. Создайте пустое приложение Win32.

    2. Скопируйте CPP-, H- и RC-файлы из каталога с компилируемой программой в каталог с новой, пустой рабочей областью.

    3. Включите файлы в пустой проект (командой Project|Add to project|Files).

    Заодно можно настроить прекомпилированные заголовки (см. выше). Проект готов к компиляции.

    Хотя пользователи Visual C++ 4.x смогут пользоваться программами из этой книги, DirectDraw AppWizard работать не будет. Если читатели проявят достаточный интерес, я могу создать соответствующую версию и поместить ее на Web-узел.

    Варианты Visual C++ 

    И последнее замечание о Visual C++. Поскольку этот продукт выпускается в трех вариантах (Learning, Professional и Enterprise), иногда читателям не удается скомпилировать проект. Для написания и тестирования программ этой книги я пользовался вариантом Professional, так что при работе с другими вариантами могут возникнуть проблемы. Впрочем, в основном это проблемы мелкие и легко излечимые. Если у вас возникнут трудности и вы решите обратиться ко мне, пожалуйста, укажите версию и вариант Visual C++.

    Советы и рекомендации 

    В этом разделе рассматривается несколько не связанных друг с другом, но полезных тем. Сначала мы поговорим об одной ошибке DirectDraw, а затем узнаем кое-что о файлах DirectX. Напоследок я скажу пару слов о видеокартах на базе чипов 3Dfx. 

    Ошибка переключения режимов DirectDraw 

    В DirectDraw есть одна ошибка, связанная с переключением режимов, которую никак не может исправить Microsoft (или тот, кто для них пишет видеодрайверы). Впервые я столкнулся с ней во время работы над предыдущей книгой. Я надеялся, что к моменту написания этой книги ошибку уже исправят, но она так и осталась.

    Проблемы возникают при активизации видеорежимов, которые отличаются по глубине пикселей от текущего активного режима Windows. Например, если Windows работает в 8-битном видеорежиме, а ваше приложение DirectDraw попытается активизировать 16-битный видеорежим, ничего не получится, даже когда новый режим вполне допустим. Если попытаться установить 8-битный видеорежим при 16-битном режиме Windows, результат будет тем же. В таких случаях DirectX выдает отладочное сообщение следующего вида:

    DDHEL: ChangeDisplaySettings LIED!!!

    DDHEL: Wanted 640x480x16 got 1024x768x8

    DDHEL: ChangeDisplaySettings FAILED: returned -1

    К счастью, это происходит только при первой попытке изменения видеорежима. Если эта попытка удалась, после этого можно установить любой видеорежим. Следовательно, у нас появляется обходной путь: если текущий видеорежим отличается по глубине пикселей от желаемого, переключение следует производить в два этапа; сначала перейдите к видеорежиму, который совпадает по глубине пикселей с текущим, а затем — к видеорежиму, нужному вам.

    Такой обходной маневр нетрудно реализовать с помощью класса DirectDrawWin, представленного в этой книге. В функции SelectInitialDisplayMode() (которая вызывается до переключения видеорежима) следует проверить глубину пикселей текущего режима. Если она отличается от требуемой, выполните «фиктивное» переключение. Программа может выглядеть так:

    int SampleWin::SelectInitialDisplayMode() {

     DWORD curdepth=GetDisplayDepth();

     int i, nummodes=GetNumDisplayModes();

     DWORD w,h,d;

     if (curdepth!=16) ddraw2->SetDisplayMode(640, 480, curdepth, 0, 0 );

     // Искать режим 640x480x16 после смены видеорежима

     for (i=0;i>nummodes;i++) {

      GetDisplayModeDimensions(i, w, h, d);

      if (w==640 && h==480 && d==16) return i;

     }

     return -1;

    }

    Мы проверяем глубину пикселей текущего видеорежима Windows функцией DirectDrawWin::GetDisplayDepth(). Нас интересует режим с 16-битными пикселями, поэтому при использовании другой глубины мы активизируем режим 640×480 с текущей глубиной, вызывая функцию SetDisplayMode() интерфейса DirectDraw. Затем можно переходить к поиску нужного режима в традиционном цикле.

    Другой выход — просто воспользоваться той глубиной пикселей, которая в данный момент установлена в Windows. Этот вариант не подойдет в тех случаях, когда работа программы зависит от определенных параметров видеорежима, но неплохо работает, если приложение поддерживает видеорежимы с различными глубинами пикселей. В частности, он встречается в программе Switch (см. главу 4), разработанной специально для поддержки любого режима. Функция SelectInitialDisplayMode() в программе Switch выглядит так:

    int SwitchWin::SelectInitialDisplayMode() {

     DWORD curdepth=GetDisplayDepth();

     int i, nummodes=GetNumDisplayModes();

     DWORD w,h,d;

     // Искать режим 640x480 с текущей глубиной пикселей

     for (i=0;i<nummodes;i++) {

      GetDisplayModeDimensions(i, w, h, d);

      if (w==640 && h==480 && d==curdepth) return i;

     }

     return 0;

    }

    Эта функция ищет режим 640×480 с текущей глубиной пикселей. Такой режим наверняка найдется, потому что 640×480 — «общий знаменатель» для всех видеорежимов и нам известно, что нужная глубина пикселей уже установлена в Windows.

    Следует заметить, что такое поведение характерно лишь для некоторых конфигураций. Иногда нужную глубину пикселей можно установить сразу, без глупых обходных маневров (например, я еще не видел, чтобы эта ошибка проявлялась на компьютерах с Windows NT). Но чтобы расширить круг рабочих конфигураций вашего приложения, пожалуй, стоит помнить об этой ошибке.

    Символическая константа INITGUID (устаревшая) 

    Как вы уже знаете, библиотека DirectX построена на базе спецификации COM, а для однозначной и систематизированной идентификации интерфейсов в COM применяются GUID. Один из заголовочных файлов COM содержит код, в котором инициализируются все конструкции, относящиеся к GUID. Такой метод инициализации COM требует, чтобы символическая константа INITGUID была определена в одном и только одном кодовом модуле, до включения заголовочных файлов COM (для DirectX заголовочные файлы COM включаются косвенно, из заголовочных файлов DirectX). Следовательно, в некоторых программах можно встретить константу INITGUID. Этот метод вызывает немало хлопот, особенно при работе с прекомпилированными заголовками, потому что он не позволяет включить заголовочные файлы DirectX в состав прекомпилированного заголовка.

    Вы не встретите INITGUID в программах на CD-ROM, потому что, начиная с DirectX 3, появилось более удачное решение — вместо того, чтобы определять INITGUID, достаточно подключить к проекту файл DXGUID.LIB. На случай, если вы не знали…

    Эмуляция версий 

    Одна из широко разрекламированных возможностей COM — управление версиями. COM-объекты устроены так, что доступ к ним может осуществляться только через строго определенные интерфейсы. В соответствии с правилами COM, интерфейс не может изменяться после его определения. Вместо этого приходится вводить новый интерфейс, который поддерживает как старые, так и новые возможности. При этом новые программы могут без опасений пользоваться новыми возможностями, а старые — работать со старыми интерфейсами, которые заведомо не изменятся. Эта схема неплохо работает и помогает обеспечить совместимость приложений DirectX со старыми и новыми runtime-частями библиотеки.

    К сожалению, в DirectX API часто используются структуры. Эти структуры являются «открытыми» — доступ к ним осуществляется непосредственно, а не через интерфейс, как для COM-объектов. Размер этих структур может изменяться (и часто изменяется) при переходе к новой версии DirectX. По этой причине каждая функция DirectX, которой в качестве аргумента передается указатель на структуру, должна обязательно получать и размер передаваемой структуры. Благодаря этому runtime-часть DirectX всегда может узнать, какая версия DirectX SDK применялась для компиляции приложения, и следовательно — какие поля входят в структуру. Проблема решена, не так ли?

    А что вы скажете насчет программы, которая была откомпилирована в DirectX 5 SDK, но затем запущена с runtime-частью DirectX 3? Если одна или несколько структур DirectX 5 были дополнены новыми полями и флагами, runtime-часть не сможет обработать эту структуру, потому что ничего не знает о появившихся в ней расширениях.

    К решению этой проблемы (которую мы ласково назовем «структурной ошибкой DirectX») можно подойти четырьмя способами:

    • поставлять нужную runtime-часть DirectX вместе с продуктом и настаивать на том, чтобы она устанавливалась на компьютерах со старыми версиями;

    • написать «умный» код, который проверяет версию установленных DLL и затем использует только структуры, поддерживаемые runtime-частью;

    • выбрать самую старую версию runtime-части, поддерживаемую вашей программой, и написать код в расчете на нее;

    • отказаться от технологии и всего, что с ней связано.

    Коммерческие программы (особенно пакеты, распространяемые на CD-ROM) в основном используют первый вариант. Он прост и позволяет всегда работать с новейшими возможностями DirectX. С другой стороны, runtime-часть DirectX 5 занимает 135 Мбайт. Это значит, что для настоящего продукта на CD-ROM остается меньше места, или вам придется поставлять дополнительный диск с DirectX. Разумеется, этот вариант не подходит для приложений, распространяемых без CD-ROM или через Internet.

    Второй вариант достаточно гибок и позволяет запустить приложение практически с любой runtime-частью, но дело это, мягко говоря, хлопотное. Обычно игра не стоит свеч — даже если вы сможете обнаружить старую runtime-часть, как компенсировать отсутствие новых возможностей? Кончится тем, что для старой версии вы будете выводить сообщение, предлагая пользователю достать новую версию DirectX.

    Третий вариант удобен, если вы не можете выбрать первый вариант и не жалеете, что лишились новых возможностей (или не особенно нуждаетесь в них). Выбирая этот вариант, следует учесть, что заголовочные файлы DirectX способны эмулировать более старые версии SDK. Следовательно, вам не придется держать старый SDK под рукой.

    Каждый компонент DirectX определяет номер версии и пользуется им в заголовочном файле. Для DirectDraw этой цели служит символическая константа DIRECTDRAW_VERSION. Например, в DirectX 3 SDK константа DIRECTDRAW_VERSION равна 0300 (завершающие нули обозначают младший номер версии).

    Обычно с помощью константы DIRECTDRAW_VERSION программа выясняет, какая версия DirectX используется в данном случае. Но что еще важнее, если вы зададите значение DIRECTDRAW_VERSION перед тем, как включать заголовочный файл DirectDraw, то в этом файле будут определены структуры, совместимые с указанным номером версии. Например, если ваша программа выглядит так:

    #define DIRECTDRAW_VERSION 0x300

    #include <ddraw.h>

    то определяемые структуры будут идентичны тем, что определялись в DirectX 3 SDK, даже если на самом деле вы работаете с DirectX 5 SDK.

    Вариант 4 выглядит соблазнительно (но я слишком люблю играть в Quake).

    Где достать DirectX SDK 

    Я уже написал две книги, в которых используется DirectX, и большинство вопросов от читателей было связано с DirectX SDK. Одни жаловались на то, что SDK не прилагается к книге, а другие просто хотели знать, где его можно достать. Я отвечал, что DirectX SDK можно бесплатно получить на Web-узле Microsoft.

    Перед выходом DirectX я также предупреждал их, что файл SDK занимает 28 Мбайт.

    Размер DirectX 3 SDK разочаровал многих читателей, а некоторые из-за этого даже не смогли работать с ним. 28 Мбайт — громадный объем для всех, у кого нет доступа к Internet через ISDN или T1, и при этом он мог бы быть и поменьше. Большую часть этих 28 Мбайт занимают примеры программ, без которых разработчик может обойтись. Строго говоря, действительно необходимы лишь H- и LIB-файлы, которые сжимаются до 100 Кб. Справочный файл сокращается до 4 Мбайт. Разумеется, эти компоненты можно было бы сделать доступными и по отдельности.

    В августе 1997 года (на момент написания этой книги) Microsoft выпустила DirectX 5 и выложила его на свою Web-страницу. На этот раз ей значительно лучше удалось удержать размер файлов в разумных пределах. Эти файлы можно найти по адресу www.microsoft.com/directx.







     

    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх