Пользователь
0,0
рейтинг
10 марта 2010 в 16:01

Разработка → Создание компактных приложений на VC++

Примечание: Автором статьи является Алексей Fahrenheit Захаренко. Хороший человек и специалист.

С экспоненциальным ростом объемов памяти и дискового пространства создание действительно маленьких программ бывает нужно достаточно редко, однако иногда встречаются задачи, когда просто жалко терять несколько сотен лишних килобайт на каждой мелкой утилите.
Данная статья рассказывает, как можно получать действительно компактные программы, используя актуальные средства разработки – Microsoft Visual Studio 2008.
В качестве примера портируем консольный архиватор 7-zip и оценим эффект от этого.

Проблематика и очевидные решения


Очевидное решение о динамической линковке библиотеки времени выполнения (CRT) не подходит, так как последние версии по умолчанию не установлены на Windows XP, которую сейчас должно поддерживать каждое уважающее себя приложение. А поставка CRT в виде DLL, конечно, не панацея, так как они имею значительный (по меркам этой статьи) объем.
Еще одно решение – это использование стандартной библиотеки msvcrt.dll, разные версии которой можно найти во всех актуальных версиях Windows. Однако библиотеки импорта, которые позволяют подключить данную DLL доступны только в Microsoft Visual Studio 6.0 – в последующих редакциях используются свои библиотеки для каждой новой версии (msvcr70.dll / msvcp70.dll, msvcr71.dll / msvcp71.dll и т.д.).
Простая попытка взять библиотеку импорта для msvcrt.dll и подключить ее к проекту, которые собирается под VS2008 к успеху приведет только в простейшем случае типа Hello World – с момента выхода Windows XP в библиотеке времени выполнения произошли существенные изменения – это и появление функций категории «secure crt», и изменения в обработке исключений, и дополнительные проверки времени выполнения.

Решение проблемы


Решение данной проблемы подсказывает Koby Kahane в своей статье Dynamically linking with MSVCRT.DLL using Visual C++ 2005.
Он провел исследование последних версий Windows Driver Kit (WDK) и выяснил интересную особенность – многие приложения и примеры, которые поставляются с WDK используют именно нужный нам CRT – msvcrt.dll. При том среда сборки в WDK – актуальная – версия компилятора последнего WDK совпадает с версией компилятора в VS2008SP1.
Детальное исследование файлов проекта показывает, что разгадка кроется в нескольких объектных файлах, которые подключаются к проекту – msvcrt_win2000.obj, msvcrt_winxp.obj, msvcrt_win2003.obj. Они, по сути, содержат «разницу» между текущим наполнением CRT и тем, которое присутствует в соответствующей имени версии Windows. Именно этим объясняется то, что размер объектных файлов для более новых Windows меньше, чем для более старых.
Итак, для подключения классической msvcrt.dll необходимо выполнить два простых шага:
  • Выключить подключение всех стандартных библиотек с помощью ключа компилятора /NODEFAULTLIB или настройки проекта Linker -> Input -> Ignore All Default Libraries
  • Необходимо подключить библиотеку импорта msvcrt.lib, которую можно найти в папке \lib\Crt\i386\msvcrt.lib WDK и подключить объектный файл, соответствующий минимальной версии Windows, на которой должно запускаться приложение. Пусть это для примера будет \lib\Crt\i386\msvcrt_winxp.obj.

Все, проект можно собирать и в простейшем случае он даже скомпилируется и корректно заработает.
Для тех, у кого в наличие нет WDK, и нет желания качать 600Мб ради нескольких мегабайт библиотек, я в конце статьи привел ссылку на архив, который содержит необходимые файлы отдельно.

Практика – подводные камни и нюансы


Итак, теперь попробуем преобразовать проект для использования в нем msvcrt.dll и посмотрим, что из этого получится.
Для примера возьмем 7-zip – достаточно хороший архиватор с открытым исходным кодом. Его исходники доступны на сайте 7-zip.org. Небольшое исследование показывает, что проект на данный момент собирается с помощью Microsoft Visual C++ 6.0, что делает его достаточно легким для портирования.
Проведем такую магическую последовательность действий:
  • Для начала убедимся, что проект правильно собирается с помощью Microsoft Visual C++ 2008. Для этого запустим makefile из папки \CPP\7zip\ (с помощью nmake). Здесь и далее все папки указаны относительно папки, в которую разархивированы исходники проекта.
  • Уже на этапе начальной сборки получим предупреждение (которое, в соответствие с настройками проекта, считается ошибкой) C4996: 'wcscpy': This function or variable may be unsafe. Consider using wcscpy_s instead.
  • Ну что ж, внесем это небольшое исправление. В файле \CPP\Windows\Security.cpp в строке 125 заменим
    wcscpy(s, MY__SE_LOCK_MEMORY_NAME);
    на
    wcscpy_s(s, 128, MY__SE_LOCK_MEMORY_NAME);
    128 символов – это максимальный размер тут же объявленного буфера
  • Теперь процесс сборки прошел успешно. На выходе были получены исполяемые файлы для всех компонентов архиватора 7z (консольная версия, файловый менеджер и т.д.). Для проверки работоспособности сборки скопируем файлы \CPP\7zip\UI\Console\O\7z.exe и \CPP\7zip\Bundles\Format7zF\O\7z.dll в одну папку и убедимся в работоспособности консольного архиватора. Также для успешного запуска понадобится положить рядом файл манифеста. Для этого проекта он дан в сопроводительных файлах к статье.

Теперь самое интересное – как заставить приложение использовать классический run-time. Для этого делаем несколько магических пассов, которые понятны всем, кто читал предыдущий раздел:
  • В файле \CPP\Build.mak в флагах линкера заменяем уже неактуальную опцию -OPT:NOWIN98 на /NODEFAULTLIB (строка 45)
  • Теперь нужно минимальными усилиями добавить к каждому проекту линковку библиотек Kernel32.lib uuid.lib. Редактировать makefile каждого проекта отдельно не хочется, поэтому в тот же \CPP\Build.mak в строку с настройками линкера добавляем необходимые для каждого проекта библиотеки и модули. Таким образом, мы получим такую строку настроек:
  • LFLAGS = $(LFLAGS) ..\..\msvcrt\msvcrt_winxp.obj ..\..\msvcrt\msvcrt.lib Kernel32.lib uuid.lib -nologo -NODEFAULTLIB -OPT:REF -OPT:ICF
  • Собираем полученный результат и проверяем. Под Win7 и виртуальной XP все работает без проблем, созданные нашей сборкой архивы успешно открываются оригинальным 7zip.

Теперь проверим, как такое портирование повлияло на производительность. Для сравнения возьмем оригинальную сборку, а также две наши сборки – одна будет использовать MSVCR90.dll, другая – MSVCRT.dll.
В таблице ниже приведены сравнения полученных размеров файлов. Портирование под новый компилятор автоматически сэкономило нам около 10% размера исполянемого файла.
MS VC 6.0 MS VC 9.0 MS VC 9.0 (msvcrt.dll)
7z.exe 150 016 байт 136 704 байта 138 240 байт
7z.dll 726 016 байт 655 872 байта 657 920 байт

Для замера скорости упакуем все бинарные файлы, полученные в результате сборки проекта (около 187Мб). Каждая из сборок проведет 10 циклов упаковки для уменьшения влияния скорости доступа к диску и кеширования, и полученные данные усредним.
MS VC 6.0 MS VC 9.0 MS VC 9.0 (msvcrt.dll)
Время упаковки 122.86 секунды 112.02 секунды 113.73 секунды

Как видим, переход на обновленный компилятор дал реальный прирост производительности (близкой к 10%, что не так плохо для приложения такого рода). Также сгенерированный бинарный файл уменьшился на те же ~10%.
Существенной разницы между использованием классического для VC90 рантайма и msvcrt.dll не замечено.

Ограничения метода


Однако следует отметить пару потенциальных недостатков и ограничений данного метода.

Нет возможности подключить отладочный рантайм msvcrtd.dll


Несмотря на то, что библиотека импорта для него присутствует в составе WDK, саму .dll найти не удалось, и, по словам Koby Kahane, Microsoft не собирается включать ее в следующие версии WDK.
Это существенно усложняет отладку приложений и провоцирует использовать приведенный в статье метод только перед релизом, что, в свою очередь, может привести к появлению трудноуловимых багов на финишной прямой выпуска продукта – ведь никто не гарантирует полную совместимость рантаймов.

MFC, ATL


Перетащить MFC или ATL для использования нового-старого рантайма динамически – достаточно сложная задача и выходит за пределы этой статьи. Однако для приложений, которые использую эти библиотеки, обычно не предъявляются жесткие требования по занимаемому на диске месту.

Файлы


В приложенном к статье архиву (1.8Мб) содержатся:
  1. Папка 7z diff содержит все изменения к проекту 7zip. Разархивируем скачиваемый с сайта 7z465.tar.bz2 и поверх корневой папки проекта накатываем приведенный патч.
  2. Папка msvcrt содержит бинарные файлы из WDK для портирования проекта под использование msvcrt.dll
  3. Папка output содержит все три сборки консольной версии 7-zip для тех, кто не доверяет моим результатам тестов и хочет их перепроверить.

Скачать файл можно по адресу: http://download.zillya.com/attach/MsVCArticlesAttach.zip

Upd. Cryptochild помог с инвайтом, спасибо!
Андрей Хижняк @Khizhnyak
карма
21,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (11)

  • +3
    А можно вообще отказаться от CRT в пользу Tiny LIBC, но такой вариант не для всех подходит.
    В любом случае, спасибо Алексею за статью, было интересно.
    • +3
      Всегда пожалуйста.
  • +3
    Не знаю насколько в наше время оправданно бороться за размер конечного бинарного файла, но ~10% прирост скорости упаковки — вот это да! =)
  • +2
    Выслал инвайт.
    • 0
      Спасибо большое.
    • +3
      Спасибо за инвайт
  • 0
    Когда то давно искал именно это решение использования msvcrt.dll в 2005-й студии
  • 0
    Статья отличная. И фамилия говорящая. Вы не родственник случайно того самого Хижняка? :)
    • 0
      К сожалению нет, не родственник. :) И к еще большему сожалению, статья не моя. :)
      Просто опубликовал по просьбе хорошего знакомого. Он вот-вот должен подтянутся сюда. Так что похвалы адресую лично Алексею.
      • 0
        Пардон. Как обычно пропустил примечания :)
  • 0
    Анатомия C Run-Time, или Как сделать программу немного меньшего размера — ещё одна полезная статья на эту же тему, хоть и давно написана.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.