Ваше первое Windows-приложение
Windows поддерживает два типа приложений: основанные на графическом интерфей се (graphical user interface, GUI) и консольные (console user interface, CUI) V приложе ний первого типа внешний интерфейс чисто графический 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) присутствует в Вашем коде, и на основа нии этого выберет подсистему и стартовую функцию из библиотеки С/С++.
Одна из частых ошибок, допускаемых теми, кто лишь начинает работать с Vi sual С++, — выбор неверного типа проекта. Например, разработчик хочет создать проект 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. |
Таблица 4-1. Глобальные переменные из библиотеки С/С++, доступные Вашим программам
Имя переменной | Тип | Описание |
_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) запускаемой программы. |