Программирование на Visual C++. Архив рассылки
Шрифт:
И да, и нет. Опция компилятора /MD указывает использовать для сборки библиотеку MSVCRT.LIB. В ней содержится только тот набор кода, который позволяет линкеру разрешить внешние связи. А сам код CRT находится в динамической библиотеке MSVCRT.DLL в системном каталоге windows. Эта многопоточная библиотека используется и некоторыми бесплатными компиляторами C/C++ для Windows, например, MinGW.
Такое решение достаточно удобно, если проект состоит из нескольких модулей – каждый из них станет меньше на объем рантайма. Кроме того, оно позволяет Microsoft исправлять ошибки в уже выпущенных программах простой заменой старой DLL на исправленную версию.
Плохая новость заключается в том, что MSVCRT.DLL существует не на всех версиях Windows. Она начала поставляться в составе ОС, начиная с Windows 95 OSR2. Приложение, запущенное в системе без этой библиотеки, выполняться не будет. Правда, таких систем становится все меньше и меньше.
Возможно, владельцы Visual C++ 5.0 заметили, что у них в результате получаются EXE-файлы куда меньшего размера, чем сказано здесь. Дело в том, что компоновщик версии 5.0 использовал выравнивание секций исполняемого файла на величину 512 байт. Начиная же с версии 6.0, при сборке приложения используется другая величина выравнивания – 4К. Это позволяет быстрее загружать такой файл в Windows 98 и более новых версиях ОС.
Вернуть прежнюю величину выравнивания можно, задав недокументированную опцию компоновщика /opt:nowin98:
Размер EXE в результате составляет менее 3-х килобайт! Но не забудьте, что такой файл будет медленнее загружаться в память, и что он по-прежнему требует наличия MSVCRT.DLL.
Если ампутация кажется вам разумной хирургической операцией, то стартовый код CRT можно выбросить из программы совсем.
Что это означает? Отказавшись от некоторых привычных удобств, которые предоставляет CRT, можно писать на C/C++, не используя возможностей, которые требуют поддержки со стороны CRT.
В мире Windows API такое решение не пугает многих. Взгляните, например, на NullSoft Installer
В самом деле, для файловых операций можно использовать функции Win API, вместо динамической памяти C++ использовать кучу (хип) Windows, для форматирования можно использовать wsprintf вместо sprintf, для сравнения строк – lstrcmp вместо strcmp и т.д.
При этом важно понимать, что CRT – это обычная библиотека, функции которой вполне можно вызывать из такой программы (как и из программы на ассемблере). Главное – это отказаться от функций, которые влекут за собой включение раздутого кода инициализации (или, в крайнем случае, включить его необходимую часть самостоятельно).
Мэтт Питрек, давний ведущий колонки "Under The Hood" в Microsoft Systems Journal (ныне – MSDN Magazine), посвятил этому вопросу цикл статей в MSJ под общей тематикой "Code Liposuction" ("обезжиривание кода"). Интересующиеся могут найти их в архиве Periodicals MSDN.
Более свежая информация содержится в его статье "Reduce EXE and DLL Size with LIBCTINY.LIB" в январском выпуске MSDN Magazine
Но не будем забираться так далеко – ведь в нашем коде нет никаких конструкторов, правда?
В данном случае можно просто указать, что функция main будет точкой входа в программу (вместо функции инициализации):
В результате также получим исполняемый файл размером менее 3 Кб (я вновь использовал опцию /opt:nowin98). Разница теперь лишь в том, что он не требует внешней CRT-библиотеки (библиотека user32.lib необходима для функции MessageBox, но она является частью ядра Windows).
Пригодность этого подхода доказывается тем, что с его помощью создано множество легких COM-компонентов. Но непонимание принципов его работы может легко завести в тупик, как видно из цитаты в начале статьи.
В составе библиотеки ATL версии 3 и более ранних имеется файл atlimpl.cpp. Он, как правило, включается в один из исходных файлов проекта (чаще всего в stdafx.cpp) с помощью директивы #include. В atlimpl.cpp находится "облегченная" реализация стартового кода CRT: в нее входят только вариант функции xxxCRTStartup, упомянутой ранее, и "обертки" для работы с динамической памятью – функции malloc, calloc, realloc, free и операторы new/delete. Они непосредственно вызывают функции Windows для работы с кучей – HeapAlloc и HeapFree. Как ни странно, этого достаточно, чтобы заставить заработать без CRT startup множество программ.
Собственно, сама эта реализация доступна, только если определен символ препроцессора _ATL_MIN_CRT. Таким образом, есть возможность легко управлять включением или исключением стартового кода CRT.
ПРИМЕЧАНИЕ
Важный момент при использовании макроса ATL_MIN_CRT: по-прежнему нельзя включать объявления глобальных переменных, классы которых имеют конструкторы или деструкторы, так как код, их вызывающий, содержится только в CRT.
Эта проблема решена в библиотеке ATL 7.0 (не удивляйтесь, как и многие другие приложения Microsoft, ATL перескочила с версии 3 на версию 7), поставляемой с компилятором MS VC++ 7.0. Тем же, кто пользуется прежними версиями компилятора, могу посоветовать воспользоваться отличной библиотекой Andrew Nosenko's ATL/AUX Library, в которой содержится код вызова конструкторов/деструкторов. Для этого необходимо включать в проект вместо atlimpl.cpp файл AuxCrt.cpp из комплекта библиотеки.
Теперь ясно, что причиной появления ошибки "unresolved external symbol _main" стало включение стартового кода CRT. То есть, была явно или неявно использована какая-либо функция, которая содержит ссылку на структуру данных, находящуюся в модуле с кодом инициализации. При включении компоновщиком в программу этого модуля возникает следующая внешняя ссылка: в теле mainCRTStartup есть вызов main. Вот и все, мы получили наше "любимое" сообщение об ошибке.