Андрей
Строганов
aka DJ-Andrey-sXe

Переходим на Microsoft Visual C++ 2005

Обновлено: 18 июля 2007

Предисловие

Microsoft внесла много значительных изменений в Visual C++ 2005. Если Вы создавали проекты в IDE старых версий, то при попытке открыть проект/решение в новой версии будет произведено обновление (разумеется с сохранением резервных копий) файлов *.dsp и *.dsw или *.sln и *.vcproj (в зависимости от версии, которую Вы использовали раньше).

Но что получится, если Ваши (или не Ваши) проекты собираются не из IDE, а с помощью .bat/.cmd или make-файлов из командной строки? Некоторые из таких проектов без изменения могут просто не собраться.

В этой статье я попытаюсь изложить действия, которые придется предпринять, чтобы сделать возможным сборку старого проекта на новом Visual C++ и избавиться от множества предупреждений во время сборки, а также объяснить причины, из-за которых они выводятся.

Deprecated-функции в CRT

Зная, что некоторые функции из CRT являются потенциальным источником проблем безопасности (чаще всего переполнение буфера), Microsoft создала для них безопасные альтернативы. Безопасные функции имеют те же имена, что и оригиналы, только у них есть суффикс _s. То есть, если Вы писали gets, то теперь MS рекомендует использовать ее безопасный аналог gets_s. Синтаксис у них такой же за исключением (на примере gets_s) добавления целочисленного параметра – максимального количества символов. Для выяснения нового синтаксиса остальных новых безопасных аналогов читайте, как обычно, MSDN.

Хорошо это или плохо? Я думаю, что на данный момент хорошо. Потому что оставлен выбор: использовать ли старые версии или переходить на новые.

А какие есть минусы? Я вижу три:

  1. Microsoft неоднократно убирала вещи, помеченные как deprecated. Мы с вами уже натерпелись ушедших ключей из инструментов студии, которые были помечены как deprecated. Это было и в версии 2003, и в версии 2005. Было вопреки тому, что написано о понятии «deprecated» (не рекомендуемое к использованию) в MSDN. Поживем, посмотрим, что выкинут в Visual Studio 2008.

    Здесь и далее оффтоп будет написан в таких как этот блоках.

    Из опыта у меня сложилось субъективное отождествление понятия deprecated буквально следующему предупреждению: «Внимание: не юзай это. В следующих версиях это будет убрано!». И это не смотря на то, что слово deprecated с английского переводится как «не рекомендованое», а не «запланировано к уничтожению».

    Больше всего расстраивают выбрасывания обратной совместимости, ведь они порождают ряд упёртых людей, которые до сих пор сидят на Visual C++ версии 6 только потому, что их проекты вхлам не собираются на новой студии или требуют ТАКИХ усилий, что проще остаться на «шестерке». Обращаюсь именно к таким: «Очнитесь, на дворе 2007 — десять лет прошло! Многие новые создаваемые сегодня SDK и проекты в лице своих разработчиков порой просто не учитывают существование VC++ 6, а о том, как 6-рка относится к стандарту — промолчим, ладно? Пожалуйста, не начинайте новые проекты на старье».

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

  2. Любая проверка, пусть даже самая простенькая, требует дополнительных затрат, а это неизбежное падение производительности. И если использование безопасных функций на Вашей машине для Ваших нужд в маленьких редко используемых утилитах повысит безопасность и практически никак не отразится на производительности, то представьте себе, как это отразится на производительности корпоративных сетевых приложений. А ведь время — деньги.

    Может быть, со мной кому-то сейчас и захотелось поспорить относительно падения производительности, сказать, что это вряд ли будет более хотя бы пары процентов при сегодняшних мощах компьютеров, а с учетом желания максимальной безопасности это не такая уж и большая плата. Да, скорее всего в зависимости от частоты вызова таких функций падение вряд ли будет ощутимым, НО! Какие объемы обрабатываемой информации Вы себе представили, с какой частотой происходят запросы, сколько человек одновременно штурмуют сервер(а)? Вы представляете себе абстрактную базу с тысячами записей или с миллионами? Вы представляете себе организацию с сотнями сотрудников или корпорацию с тысячами сотрудников? И, наконец, вы представляете себе серверы/кластеры, оснащенные по последнему слову техники за бешеные деньги или наши российские реалии? Призадумались?…

  3. Зная, сколько проблем безопасности можно было бы избежать во многих программах, будь мы все изначально обязаны неизбежно проверять границы буферов и прочее, кажется, что давно пора было что-то подобное сделать и все — проблем нет. Мысль обманчивая. Идея, безусловно, отличная (хотя и не новая). Но что бы там Microsoft не придумывали, это все равно не избавит Вас от всех возможных проблем.

На мой взгляд, особых причин тотально переходить на новые функции нет. Почему? А все просто: есть куски кода, в которых в манипулирование данными пользователь вмешаться не способен. Эти проверенные и отлаженные куски кода можно и нужно оставлять без проверок. Но когда дело касается ЛЮБЫХ мест в программе, в которые пользователь или какие-то другие внешние по отношению к программе обстоятельства могут каким бы то ни было образом взаимодействовать с программой, вот тут самое место для таких функций или собственных оберток с проверкой (что, кстати, не создает таких проблем при портировании, как *_s-функции). Итак, еще раз (ИМХО): лучше тщательно продумывать сценарии работы пользователей, анализировать места, которые нуждаются в контроле входных данных, и двигаться в сторону безопасности только там, где это действительно необходимо, а не плодить оверхеды под руководством MS во всех местах.

Итак, что непосредственно нужно сделать, чтобы cl не беспокоил Вас по поводу Deprecated-функций (а в больших проектах эта ругань достигает достаточно приличных размеров): Перед включением заголовочных файлов CRT нужно написать:

#define _CRT_SECURE_NO_DEPRECATE

В противном случае cl будет атаковать Вас сообщениями вроде:

test.cpp(6) : warning C4996: 'gets' was declared deprecated...
test.cpp(7) : warning C4996: 'fopen' was declared deprecated...
test.cpp(11) : warning C4996: 'sprintf' was declared deprecated...

Однопоточной CRT больше нет

Как мы помним, издавна в Microsoft Visual C++ были разные типы CRT: отладочные и релизные, динамически и статически линкуемые, многопоточные и однопоточные. Так вот обращаю Ваше внимание на последнюю пару. В новой версии 2005 отсутствует однопоточная версия CRT. Мне жаль, что Microsoft так поступила, потому что, во-первых, статическая однопоточная версия была меньше многопоточной, во-вторых, по понятным причинам однопоточная версия работала эффективнее многопоточной в программах (преимущественно консольных), в которых вся работа всегда проходила в одном потоке. Для таких программ использование многопоточной версии CRT было вредно по всем параметрам. И я, и Вы этим давно и успешно пользовались. И вот те на… Зачем это сделали я объяснить не могу. Так что смело сносите ключ компилятора -ML. Он больщше не знает, что это такое.

Как теперь быть без однопоточной CRT?

Что делать, если однопоточной CRT больше нет, а использовать многопоточную CRT со всеми ее свойствами не очень хочется? Придать однопоточное поведение многопоточной CRT частично можно.

Далее временно под словом поток я буду понимать поток ввода/вывода (I/O Stream), а под словом нить (thread) — поток программного кода, просто потому что эти две вещи в переводе на русский называются одинаково (что вызывает путаницу), хотя слово нить мне как-то не очень симпатизирует в качестве обозначения понятия thread)

  1. Итак, в Вашей программе всего одна нить, а в многопоточной (multithreaded) CRT все потоковые функции блокируют нить, чтобы гарантировать отсутствие конфликтов с другими нитями. Блокировка нити (и, конечно же, разблокировка после) отнимает время. Хотелось бы не блокировать единственную нить. Для этого опять же перед включением заголовков CRT пишем следующую директиву препроцессора:
    #define _CRT_DISABLE_PERFCRIT_LOCKS
    

    После этого функции ввода/вывода будут вести себя как в однопоточной (singlethreaded) версии CRT.

  2. Каждая нить по умолчанию может иметь собственную локаль. Если Вам не нужная такая функциональность, то Вам не нужны и накладные расходы. Чтобы избавиться от такого поведения, нужно:
    #include <locale.h>
    

    и в начале программы выполнить:

    _configthreadlocale(_DISABLE_PER_THREAD_LOCALE);
    
  3. Ну и, естественно, используйте только версию статической компоновки для релиза. (В отладке совсем другое дело – быстрее собирается с DLL CRT.) Проверенно: Static CRT всегда работает быстрее. (Например, в моей игре FPS больше со Static CRT на разных компьютерах с очень разными конфигурациями по сравнению с Dynamic CRT.) Если миллисекунды роли не играют и в Вашем проекте много модулей, использующих CRT то из соображения экономии лучше выбрать CRT в исполнении DLL. Но если же бинарник всего один, то лучше Static CRT. За ранние версии я не ручаюсь, а вот начиная с 5 версии в сумме BIN + Dynamic linked CRT всегда был больше, чем BIN + Static linked CRT.

Прекомпилированные заголовочные файлы (Precompiled headers)

Как раньше было замечательно: написал ключик -YX и забыл. Нет заголовков – создаются, есть – используются… Красота! Была. Теперь этого ключа не существует. Остались только -Yc и -Yu. Поначалу мне показалось, что теперь пользоваться PCH будет не так удобно, но позже выяснилось, что в действительности заголовки, которые было бы разумно всегда иметь предварительно cкомпилиорванными добавляются/удаляются/изменяются настолько редко, что уже сейчас -YX кажется плюшкой для совсем уж ленивых.

IDE

В IDE (кроме обновленного интерфейса, который, конечно, первым сразу же бросается в глаза) существенных изменений и дополнений нет. От IDE из 2003 версии новую среду отличает лишь несколько новых панелей, окно растолстевшееся окно настройки. Обновились Wizard’ы, обогащена функциями технология IntelliSense, подсветка синтаксиса по-прежнему проигрывает colorer’у Игоря Русских. Появилась возможность собирать несколько проектов параллельно, используя при этом ресурсы нескольких процессоров.

OpenMP

В Visual C++ 8 появилась полная поддержка расширений языка новым стандартом OpenMP 2. Это API, позволяющий писать эффективные приложения для мультипроцессорных систем более удобно, нежели другие стандарты. Сегодня уже доступны (кстати, не так уж и дорого!) двухъядерные процессоры, процессоры с технологией HyperThreading (суперкомпьютеры, пожалуй, оставим за пределами статьи), т. е. пользователям уже доступны аппаратные технологии, позволяющие решать задачи быстрее за счет распараллеливания их на нескольких процессорах. Поэтому сегодня при разработке игр, мультимедиа-кодеков и т.п. ПО стоит задумываться о программировании с поддержкой параллельных вычислений, т.к. на многопроцессорной системе программный код, написанный в расчете на один процессор быстрее выполняться не станет.

Поддержка OpenMP включается ключом компилятора /openmp, а в коде проверяется определенностью символа препроцессора _OPENMP. Управление распараллеливанием осуществляется при помощи директив препроцессора и функций. В случае, если Вы ограничиваетесь лишь директивами, то больше ничего не нужно, а если требуется вызывать функции, то в программу необходимо включить omp.h и линковать на библиотеку vcompd.dll/vcomp.dll (debug/release соответственно).. Если Вас заинтересовала эта технология, то я рекомендую прочитать статью Реализация многопоточности без лишних усилий. В ней довольно простым языком и с примерами показаны основы параллельного программирования с OpenMP.

Собираем Perl::DBI и DBD::InterBase

Если интересно, что это, даю ссылки: Perl::DBI в Википедии, Модуль DBD::InterBase в CPAN

Сейчас я на конкретном примере покажу, с чем придется столкнуться при переезде на Visual C++ 2005. Эти модули для языка Perl были выбраны не случайно. Я помню, что при их установке приходится кое-что сделать, прежде чем что-то заработает. Заодно, может быть, встречу еще что-нибудь упущенное ранее о VC++ 2005 и напишу здесь.

Итак, беру DBI версии 1.50 (свежайший на момент написания). Ставить буду на ActivePerl версии 5.8.8 от ActiveState. Развернул архив, пошел в получившуюся папочку. Пускаю там Makefile.PL, отработал. Командую nmake… синий экран! Здрастьте, давно не виделись. Если и у Вас то же самое, знайте, в этом виноват Outpost Firewall Pro. Тут придется его либо снести, либо компилировать в безопасном режиме (чтобы он не мешался). При выполнении nmake даже с не запущенным Аутпостом (не знаю, как в других случаях, но на DBI стабильно) винда падает в синий экран. Идем дальше. Снова nmake. Повалились warning’и о deprecated функциях. Добавляю в начало DBI.xs и Driver_xst.h строку #define _CRT_SECURE_NO_DEPRECATE. После этого nmake clean, Makefile.PL и снова nmake. Собралось. На первый взгляд это так. На самом деле не работает. Дело оказалось в том (опять же подчеркиваю, в данном конкретном случае!), что dll-ка DBI собирается связанной с MSVCR80.dll (это CRT), и в таком варианте не работает. Почему это происходит, сколько раз ни ставил – никогда не разбирался и сейчас не буду. Тем более, что CRT в DLL не устраивает.

Кстати, при линковке на CRT DLL новый линкер помимо непосредственно связывания с DLL через таблицу импорта творит еще и xml-файлик подобный манифесту для XP Common Controls и вписывает туда Assembly Microsoft.VC80.CRT, что по-видимому и означает как раз тот самый CRT DLL, но, что интересно, если на машине с виндой не ниже XP нет такого файла, ОС говорит, что приложение неверно настроено и что его переустановка решает проблему. Извините!!! Если под «неверно настроено» Microsoft теперь понимает отсутствие DLL, то у меня просто нет слов. Ну и откуда, спрашивается, пользователь (не такой как я или Вы, уважаемый читатель, раз Вы до сюда дочитали) узнает о том, что именно является причиной неработоспособности приложения (а именно: отсутствие некой dll), и уж тем более что нужно сделать (а именно: временно переместить манифест из папки приложения), чтобы выяснить, в какой именно dll несчастное приложение испытывает нужду?

Откуда берётся связь с MSVCR80.dll? Лезем в Makefile. Там и лежит ответ на вопрос. Меняем везде msvcrt.lib на libcmt.lib и -MD на -MT. Ключ -MT по-прежнему можно использовать, хотя смысла в этом нет. Многопоточная CRT в VC++ 8.0 используется по умолчанию. Makefile отредактировали, теперь пробуем снова. Собралось. dumpbin /imports DBI.dll показывает, что все получилось как хотелось. nmake install.

Теперь давайте поставим DBD::InterBase (0.44 на сегодня свежайшая версия). Опыт борьбы с DBI здесь пригодится, но и тут не без особенностей. Поначалу все также: пускаю Makefile.PL, отвечаю на вопросы, где у меня FireBird, где его SDK… Oops!

I can't find your MS VC++ installation.
DBD::InterBase cannot build.

Makefile.PL подсказывает, что его разработчик до сих пор не догадывается о существовании каких-то еще версий VC++ кроме 6.0 и 7.0 (утрирую). Поможем скрипту найти VC++ 8.0: меняем строку

$sw->{"Microsoft/VisualStudio/7.0/Setup/VC"};
на
$sw->{"Microsoft/VisualStudio/8.0/Setup/VC"};

Пробуем, идет дальше, но валится на выполнении теста по созданию базы с помощью isql. Позаменял кое-где юниксовые slash’ы на виндовые – не помогло. Тогда в ответ на «Full path to your test database:» пишу «localhost:c:\test.gdb» (в linux можно было хост не указывать, создавал на локальном). Он мне в ответ: «localhost:c:\test.fdb does not exist.» (а откуда ему быть-то до создания?). Создал, снёс и сотворил-таки makefile. nmake. Снова наслушался про deprecated. Заткнул при помощи #define _CRT_SECURE_NO_DEPRECATE. Теперь без лишнего шума можно разглядеть, что все собирается. Но и здесь линкуемся с пресловутой MSVCR80.DLL. Делаю s/msvcrt\.lib/libcmt.lib/gi; и class=»g»>s/-MD/-MT/gi; в Makefile. Затем снова компилирую и… Eureka! dumpbin (из зависимостей лишь kernel, perl, fbclient), nmake install, работает :)

Еще раз, кратко и по делу

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

Удалить ключи компилятора:

-ML
-YX
-Og
-G3, -G4, -G5, -G6, -G7, -GB

Заменить ключи компилятора:

-GX на -EH (Подробнее в MSDN)
-Gf на -GF (Подробнее в MSDN)
-Op на -fp (Подробнее в MSDN)
-Fr на -FR (Подробнее в MSDN)
// Не выводить предупреждения о небезопасных функциях
#define _CRT_SECURE_NO_DEPRECATE

// Использовать неблокирующие I/O-функции
// Улучшает производительность однопоточных программ
#define _CRT_DISABLE_PERFCRIT_LOCKS

// Одна локаль на все потоки
#include <locale.h>
_configthreadlocale(_DISABLE_PER_THREAD_LOCALE);


Если компилятор ругается блоками наподобие:
...\somefile(123) : see reference to class template instantiation ...
with
[
    ...
]
то надо добавить ключ -EHsc

Следующий ключ заставит компилироваться код, который рассчитывает на то, что переменные, объявленные в цикле for продолжают существовать после тела цикла. В противном случае такие переменные будут являться локальными только для тела цикла (по стандарту).

-Zc:forScope-

Размер time_t по умолчанию теперь равен 64 бита.

Материал не претендует на полноту охвата и 100% достоверность. Наверняка в будущем еще найдется чем пополнить и улучшить эту статью. Более подробно об отличиях новой версии VC++, как обычно, можно почитать в MSDN. А пока остается Вам пожелать легкого перехода и поменьше граблей.

Добавить комментарий: