Ваше первое Windows-приложение
Windows поддерживает два типа приложений: основанные на графическом интерфейсе (graphical user interface, GUI) и консольные (console user interface, CUI). У приложений первого типа внешний интерфейс чисто графический GUI-приложения создают окна, имеют меню, взаимодействуют с пользователем через диалоговые окна и вообще пользуются всей стандартной "Windows'oвской" начинкой. Почти все стандартные программы Windows — Notepad, Calculator, Wordpad и др — являются GUI-приложениями. Приложения консольного типа работают в текстовом режиме: они не формируют окна, не обрабатывают сообщения и не требуют GUI. И хотя консольные приложения на экране тоже размещаются в окне, в нем содержится только текст. Командные процессоры вроде Cmd.exe (в Windows 2000) или Command.com (в Windows 98) — типичные образцы подобных приложений.
Вместе с тем граница между двумя типами приложений весьма условна. Можно, например, создать консольное приложение, способное отображать диалоговые окна. Скажем, в командном процессоре вполне может быть специальная команда, открывающая графическое диалоговое окно со списком команд, вроде мелочь — а избавляет от запоминания лишней информации. В то же время можно создать и GUI-приложение, выводящее текстовые строки в консольное окно. Я сам часто писал такие пpoграммы: создав консольное окно, я пересылал в него отладочную информацию, связанную с исполняемым приложением. Но, конечно, графический интерфейс предпочтительнее, чем старомодный текстовый. Как показывает опыт, приложения на основе GUI "дружественнее" к пользователю, а значит и более популярны.
Когда Вы создаете проект приложения, Microsoft Visual C++ устанавливает такие ключи для компоновщика, чтобы в исполняемом файле был указан соответствующий тип подсистемы. Для CUI-программ используется ключ /SUBSYSTEM:CONSOLE, а для GUI-приложений — /SUBSYSTEM:WINDOWS Когда пользователь запускает приложение, загрузчик операционной системы проверяет помер подсистемы, хранящийся в заголовке образа исполняемого файла, и определяет, что это за программа — GUI или СUI.
Если номер указывает на приложение последнего типа, загрузчик автоматически создает текстовое консольное окно, а если номер свидетельствует о противоположном — просто загружает программу в память. После того как приложение начинает работать, операционная система больше не интересуется, к какому типу оно относится.
Во всех Windows-приложениях должна быть входная функция за реализацию которой отвечаете Вы. Существует четыре такие функции:
int WINAPI WinMain( HINSTANCE hinstExe, HINSTANCE, PSTR pszCmdLine, int nCmdShow);
int WINAPT wWinMain( HINSTANCE hinstExe, HINSTANCE, PWSTR pszCmdLine, int nCmdShow);
int __cdecl main( int argc, char *argv[], char *envp[]);
int _cdecl wmain( int argc, wchar_t *argv[], wchar_t *envp[]);
На самом деле входная функция операционной системой не вызывается. Вместо этого происходит обращение к стартовой функции из библиотеки С/С++. Она инициализирует библиотеку С/С++, чтобы можно было вызывать такие функции, как malloc и free, а также обеспечивает корректное создание любых объявленных Вами глобальных и статических С++-объектов до того, как начнется выполнение Вашего кода. В следующей таблице показано, в каких случаях реализуются те или иные входные функции.
Тип приложения | Входная функция | Стартовая функция, встраиваемая в Ваш исполняемый файл |
GUI-приложение, работающее с ANSI-символами и строками | WinMain | WinMainCRTStartup |
GUI-приложение, работающее с Unicode-символами и строками | wWinMain | wWinMainCRTStartup |
GUI-приложение, работающее с ANSI-символами и строками |
main | mainCRTStartup |
GUI-приложение, работающее с Unicode-символами и строками | wmain | wmainCRTStartup |
Аналогичным образом, если ладан ключ /SUBSYSTEM:CONSOLE, компоновщик ищет в коде функцию main или wmain и выбирает соответственно mainCRTStartup или wmainCRTStartup; если в коде нет ни main, ни wmain, сообщается о той же ошибке — "unresolved external symbol".
Но не многие знают, что в проекте можно вообще не указывать ключ /SUBSYSTEM компоновщика. Если Вы так и сделаете, компоновщик будет сам определять подсистему для Вашего приложения. При компоновке он проверит, какая из четырех функций (WinMain, wWinMain, main или wmain) присутствует в Вашем коде, и на основании этого выберет подсистему и стартовую функцию из библиотеки С/С++.
Одна из частых ошибок, допускаемых теми, кто лишь начинает работать с Visual С++, — выбор неверного типа проекта. Например, разработчик хочет создать проект Win32 Application, а сам включает в код функцию main. При его сборке он получает сообщение об ошибке, так как для проекта Win32 Application в командной строке компоновщика автоматически указывается ключ /SUBSYSTEM:WlNDOWS, который требует присутствия в коде функции WinMain или wWinMatn. В этот момент раз работчик может выбрать один из четырех вариантов дальнейших действий:
Все стартовые функции из библиотеки С/С++ делают практически одно и то же. Разница лишь в том, какие строки они обрабатывают (в ANSI или Unicode) и какую входную функцию вызывают после инициализации библиотеки. Кстати, с Visual C++ поставляется исходный код этой библиотеки, и стартовые функции находятся в файле CRt0.c. А теперь рассмотрим, какие операции они выполняют:
Закончив эти операции, стартовая функция обращается к входной функции в Вашей программе. Если Вы написали ее в виде wWinMain, то она вызывается так:
GetStartupInfo(&StartupInfo);
int nMainRetVal = wWinMain(GetMjduleHandle(NULL), NULL, pszCommandLineUnicode, (StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ? StartupInfo.wShowWindow , SW_SHOWDEFAULT);
А если Вы предпочли WinMain, то:
GetStartupInfo(&StartupInfo);
int nMainReLVal = WinMain(GetModuleHandle(NULL), NULL, pszCommandLineANSI, (StartupInfo.dwFlags & STARTF_USESHOWWINDOW) ? Startupinfo.wShowWindow , SW_SHOWDEFAULT);
И, наконец, то же самое для функций wmain и main.
int nMainRetVal = wmain(__argc, __wargv, _wenviron}; int nMainRetVal = main(_argc, __argv, _environ);
Когда Ваша входняя функция возвращает управление, стартовая обращается к функции exit библиотеки С/С++ и передает ей значение nMainRetVal. Функция exit выполняет следующие операции:
Это заставляет операционную систему уничтожить Ваш процесс и установить код его завершения.
Имя переменной | Тип | Описание |
_osver | unsigned int | Версия сборки операционной системы. Например, у Windows 2000 Beta 3 этот номер был 2031, соответственно _osver равна 2031. |
_winmajor | unsigned int | Основной номер версии Windows в шестнадцатерич ной форме. Для Windows 2000 это значение равно 5. |
Имя переменной | Тип | Описание |
_winminor | unsigned int | Дополнительный номер версии Windows в шестнадца теричной форме Для Windows 2000 это значение равно 0 |
_winver | unsigned int | Вычисляется как ( winmajor << 8) + _winminor. |
__argc | unsigned int | Количество аргументов, переданных в командной строке |
__argv _ _wargv | char ** wchar_t ** | Массив размером __argc с указателями на ANSI- или Unicode-строки. Каждый элемент массива указывает на один из аргументов командной строки. |
_environ _wenviron | char ** wchar_t ** | Массив указателей на ANSI- или Unicode-строки. Каждый элемент массива указывает на строку — переменную окружения. |
_pgmptr _wpgmptr | char ** wchar_t** | Полный путь и имя (в ANSI или Unicode) запускаемой программы. |