суббота, 2 апреля 2016 г.

Правильная работа с консольными приложениями в Visual Studio



Как добиться паузы перед завершением программы

При запуске консольного проекта из Visual Studio в конце работы программы окно может автоматически закрыться. Так быстро, что юный программист даже не успеет увидеть результат работы свой программы. Для борьбы с этим практикуют разные способы. Как ни странно, у новичков и у профессионалов разные подходы.
  • Самый отвратительный способ - вставка кода "system("pause")" перед завершением программы. Так юный программист не только модифицирует код, но и вставляет жуткий костыль, который при других способах запуска или на другой ОС работать не будет.
  • Правильный, но неудобный способ: воткнуть точку останова (breakpoint) отладчика перед завершением.
  • Тоже правильный, и при этом удобный способ: запускать программу без отладчика (хоткей "Ctrl+F5" вместо "F5")

"Почему Ctrl+F5" не работает?

Иногда запуск без отладки не работает, как надо: консоль не выводит надпись "Press any key to continue..." и просто закрывается без ожидания.
Скорее всего, вы создавали проект через шаблон "Empty Project". В ОС Windows каждое приложение привязано к одной из подсистем. Это могут быть:
  •  MS-DOS совместимая подсистема CONSOLE
  • Обычная подсистема WINDOWS для графических приложений.
  • Специальные подсистемы для драйверов, для POSIX-приложений и других задач.
Шаблон Empty Project создаёт приложения для подсистемы WINDOWS, а не подсистемы CONSOLE. Эта подсистема не подразумевает создания окна консоли при запуске, потому что графическим приложениям оно ни к чему. При запуске программы из VS через Ctrl+F5 консоль всё же появляется, но закрывается без обычного "Press any key to continue...".

Чтобы исправить подсистему, зайдите в настройки проекта, на вкладку Linker, в раздел System и смените значение свойства "SubSystem" на "Console". Если у вас русскоязычная Visual Studio, я Вам искренне сочувствую.

Прочее

Остальные советы будут менее подробными, так как я не могу описать все технические детали в рамках одного поста.
  • Стремитесь использовать в консольных приложениях автоматические тесты вместо ручного тестирования. Тесты могут быть реализованы как модульные тесты (например, с библиотекой Boost.Test) или как автоматический запуск собранной программы и проверка её вывода с ожидаемым (такие тесты можно сделать на основе bat-файлов, shell-скриптов или скриптов на Python).

четверг, 24 декабря 2015 г.

Окно консоли в оконном приложении Windows

Подсистемы в Windows

Для Windows исполняемый файл — это не просто кусок кода. Обычный exe имеет стандартный заголовок и целый ряд сегментов. Подробнее о структуре exe-файла можно узнать на википедии.

Исполняемые файлы, предназначенные для ОС Windows, в заголовке имеют запись о «подсистеме» (subsystem). От этой записи зависит, какое окружение создаст ОС Windows во время запуска и работы процесса. Для прикладных нужд важны лишь две подсистемы:
  • Подсистема консольных приложений (Console) заставляет ОС Windows создать окно, эмулирующее консоль DOS. Если приложение не является консольным, то это окно явно будет лишним.
  • Подсистема оконных приложений (Window) приводит к тому, что эмулятор консоли DOS не создаётся.
Приложение для подсистемы console выглядит примерно так:

Убираем окно консоли

Окно эмуляции консоли DOS бывает полезным при отладке, потому что к нему прикрепляются стандартные потоки ввода/вывода (то есть stdin, stdout, stderr). Но в релизной версии лучше обойтись без лишнего окна.

Убрать окно можно в два шага
  • Изменить подсистему в настройках компоновщика (linker) для проекта, дающего на выходе «.exe».
  • Разобраться с main/WinMain
Подсистема меняется просто:


Разница между main и WinMain

Функция main — стандартная точка входа для программ, написанных на языках C и C++. К сожалению, для оконных приложений разработчики Windows решили сделать иначе: если подсистемой приложения указана подсистема «Window», то выполнение программы начнётся с функции WinMain (wWinMain для unicode-программ), у которой совсем иные параметры.

Если у готового приложения поменять подсистему с console на window, компоновщик выдаст ошибку сборки: функция WinMain не найдена. Варианты решения:
  • Для приложений, основанных на winapi (WTL, MFC) можно определить функцию WinMain так, как рассказано на MSDN
  • Для приложений, использующих кроссплатформенные библиотеки (SFML, SDL), есть распространённое решение, подготовленное авторами библиотек. Нужно просто указать компоновщику на библиотеку sfml-main.lib (или SDLmain.lib в случае SDL). Единственная задача такой библиотечки — предоставлять свою реализацию функции WinMain, которая просто вызывает функцию main, определённую в исходном коде программы.

воскресенье, 6 декабря 2015 г.

Подключение динамической библиотеки к C++ проекту в VS2013

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

Найти и обезвредить

Нам нужна библиотечка, весь полезный код которой лежит в “*.dll”. Но откуда компилятор узнает список доступных в DLL функций и способы их вызова?
В самом файле часть информации имеется, но её недостаточно — как минимум, DLL не хранит информацию о типе возвращаемого функцией значения.
Всё это есть в заголовочных файлах, с которыми библиотека была собрана. А для полной сборки проекта в Visual Studio нам нужно скачать архив, содержащий:
  • Заголовочные файлы “*.h”
  • Файлы с информацией для компоновщика “.lib”
  • Файлы с заранее скомпилированной реализацией библиотеки “.dll”
Рассмотрим для примера zlib (http://www.zlib.net/). Страница этой библиотеки не очень понятна, но можно найти строку “zlib compiled DLL” для последней версии библиотеки. Архив имеет следующий вид:

 C vs C++

Имеет большое значение язык, на котором написана сама библиотека. Если C, то особых сложностей нет. Если C++, то библиотека, собранная с одним ABI, не будет работать с проектом, который собирается с другим ABI. Кратко про ABI:
  • Вот статья на Википедии
  • У платформ 32-бит (x86) и 64-бит (x64) разные ABI
  • У компилятора MinGW - свой ABI
  • У каждой версии и каждой целевой платформы в Visual C++ - свой ABI
  • Название используемой платформы можно посмотреть в настройках сборки проекта

Библиотека libz написана на C, так что имеет значение лишь битность — 32 или 64. Библиотека SFML написана на C++, и важно выбрать нужный компилятор и платформу. На странице библиотеки нетрудно выбрать архив со сборкой под Windows и нужную версию Visual Studio.

Правим настройки компиляции проекта

Заголовочные файлы, как обычно, лежат в “include”. Их надо подключить к проекту, но не стоит использовать абсолютные пути, иначе потом на другой машине не соберёте. К счастью, у системы сборки Visual C++ есть удобная переменная “SolutionDir”, которую мы применим:



После этого шага настройки вы сможете использовать “#include <zlib.h>” в своём коде, но при попытке использовать любую функцию получите ошибку стадии компоновки (linker error с кодом ошибки вида “LNK1234”). То есть мы видим объявление функции, но где находится реализация — пока неизвестно.

Правим настройки компоновки проекта

Реализация всех функций zlib заранее собрана в машинный код и лежит в DLL. Но для компоновки под платформой Windows потребуются ещё и файлы “*.lib”. Используя ту же переменную систему сборки, укажем дополнительный путь к lib-файлам и имя lib-файла:

После этого шага программа должна собраться, но не сможет запустится (если только кто-то не положил “zlib1.dll” вам в папку System32). При попытке запуска — сообщение об отсутствии DLL.

Обеспечиваем запуск программы

Проще всего после сборки скопировать DLL в ту же папку, где лежит (точнее, будет лежать) EXE. Через настройки сборки проекта это делается очень легко путём запуска внешней команды copy:

Если после этого шага ваша программа, использующая zlib, не запустилась — значит, вы что-то напутали в подключении или в своём коде.