Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Создание эффективных приложений для Windows Джеффри Рихтер 2004 (Книга).pdf
Скачиваний:
375
Добавлен:
15.06.2014
Размер:
8.44 Mб
Скачать

Чтобы всс эти потоки работали, операционная система отводит каждому из них определенное процессорное время. Выделяя потокам отрезки времени (называемые квантами) по принципу карусели, она создает тем самым иллюзию одновременного выполненияпотоков Рис 4-1 иллюстрирует распределение процессорноговремени между потоками па машине с одним процессором. Если в машине установлено более одного процессора, алгоритм работы операционной системы значительно усложняет ся (в этом случае система стремится сбалансировать нагрузку между процессорами).

При создании процесса первый (точнее, первичный) поток создается системой автоматически. Далее этот поток может породить другие потоки, те в свою очередь — новые и т. д.

WINDOWS 2000

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

WINDOWS 98

Windows 98 работает только с одним процессором. Даже если у компьютера несколько процессоров, под управлением Windows 98 действует лишь один из них

— остальные простаивают.

Ваше первое 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-приложение, работающее

main

mainCRTStartup

с ANSI-символами и строками

 

 

GUI-приложение, работающее с Unicode-

wmain

wmainCRTStartup

символами и строками

 

 

 

 

 

Нужную стартовую функцию в библиотеке С/С++ выбирает компоновщик при сборке исполняемого файла. Если указан ключ /SUBSYSTEM:WINDOWS, компоновщик ищет в Вашем коде функцию WinMain или wWinMain, Если ни одной из них нет, он сообщает об ошибке "unresolved external symbol" ("неразрешенный внешний символ"); в ином случае

— выбирает WtnMainCRTStartup или wWinMainCRTStartup соответственно.

Аналогичным образом, если ладан ключ /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 В этот момент раз работчик может выбрать один из четырех вариантов дальнейших действий:

заменить main на WinMain Как правило, это не лучший вариант, поскольку разработчик скорее всего и хотел создать консольное приложение,

открыть новый проект, на этот раз — Win32 Console Application, и перенести в него все модули кода. Этот вариант весьма утомителен, и возникает ощущение, будто начинаешь все заново,

открыть вкладку Link в диалоговом окне Project Settings и заменить ключ

/SUBSYSTEM:WINDOWS на /SUBSYSTEM:CONSOLK. Некоторые думают, что это единственный вариант,

открыть вкладку Link в диалоговом окне Project Settings и вообще убрать ключ /SUBSYSTEM:WINDOWS. Я предпочитаю именно этот способ, потому что он самый гибкий. Компоновщик сам сделает все, что надо, в зависимости от вход ной функции, которую Вы реализуете в своем коде, Никак не пойму, почему это не предлагается по умолчанию при создании нового проекта Win32 Appli cation или

Win32 Console Application.

Все стартовые функции из библиотеки С/С++ делают практически одно и то же. Разница лишь в том, какие строки они обрабатывают (в ANSI или Unicode) и какую входную функцию вызывают после инициализации библиотеки. Кстати, с Visual C++ поставляется исходный код этой библиотеки, и стартовые функции находятся в фай ле CRt0.c. А теперь рассмотрим, какие операции они выполняют:

считывают указатель на полную командную строку нового процесса; считывают указатель на переменные окружения нового процесса;

инициализируют глобальные переменные из библиотеки С/С++, доступ к ко торым из Вашего кода обеспечивается включением файла StdLib.h. Список этих переменных приведен в таблице 4-1; инициализируют кучу (динамически распределяемую область памяти), исполь

зуемую С-функциями выделения памяти (т. с. malloc и calloc} и другими про цедурами низкоуровневого ввода-вывода; вызывают конструкторы всех глобальных и статических объектов С++-классов.

Закончив эти операции, стартовая функция обращается к входной функции в Вашей программе. Если Вы написали ее в виде 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 выполняет следующие операции:

вызывает всс функции, зарегистрированные вызовами функции _onexit; вызывает деструкторы всех глобальных и статических объектов С++-классов;

вызывает Windows-функцию ExifProcess, передавая ей значение nMainRetVal. Это заставляет операционную систему уничтожить Ваш процесс и установить код его завершения.

 

Имя

 

Тип

 

Описание

 

 

переменной

 

 

 

 

 

_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 _

char ** wchar_t **

Массив размером __argc с указателями на ANSIили Unicode-строки.

_wargv

 

 

Каждый элемент массива указывает на один из аргументов командной

 

 

 

 

строки.

 

 

_environ

char ** wchar_t **

Массив указателей на ANSIили Unicode-строки. Каждый элемент массива

_wenviron

 

 

указывает на строку — переменную окружения.

 

 

 

_pgmptr

char ** wchar_t**

Полный путь и имя (в ANSI или Unicode) запускаемой программы.

_wpgmptr

 

 

 

 

 

Описатель экземпляра процесса

Любому EXEили DLL-модулю, загружаемому в адресное пространство процесса, при сваивается уникальный описатель экземпляра. Описатель экземпляра Вашего EXE файла передается как первый параметр функции (w)WinMain - hinstExe. Это значе ние обычно требуется при вызовах функций, загружающих те или иные ресурсы. На пример, чтобы загрузить из образа ЕХЕ-файла такой ресурс, как значок, надо вызвать:

HICON LoadIcon( HINSTANCE hinst, PCTSTR pszIcori);

Первый параметр в LoadIcon указывает, в каком файле (EXE или DLL) содержится интересующий Вас ресурс. Многие приложения сохраняют параметр hinstExe функ ции (w)WinMain в глобальной переменной, благодаря чему он доступен из любой части кода ЕХЕ-файла.

В документации Platform SDK утверждается, что некоторые Windows-функции требуют параметр типа HMODULE. Пример — функция GetModuleFileName

DWORD GetModuleFileName( HMODULE hinstModule, PTSTR pszPath, DWORD cchPath);

NOTE:

Как оказалось, HMODULE и HINSTANCE — это идно и то же. Встретив в доку ментации указание передать какой-то функции HMODULE, смело передавайте HINSTANCE, и наоборот. Они существуют в таком виде лишь потому, что в l6 разрядпой Windows идентифицировали совершенно разные вещи.

Истинное значение параметра hinstExe функции (w)WinMain — базовый адрес в памяти, определяющий ту область в адресном пространстве процесса, куда был заг ружен образ данного ЕХЕ-файла, Например, если система открывает исполняемый файл и загружает его содержимое по адресу 0x00400000, то hinstExe функции (w)Win Main получает значение 0x00400000.

Базовый адрес, но которому загружается приложение, определяется компоновщи ком. Разные компоновщики выбирают и разные (no умолчанию) базовые адреса. Ком поновщик Visual С++ использует по умолчанию базовый адрес 0x00400000 — самый нижний в Windows 98, начиная с которого в ней допускается загрузка образа испол няемого файла. Указав параметр /BASE: адрес (в случае компоновщика от Microsoft), можно изменить базовый адрес, по которому будет загружаться приложение.

При попытке загрузить исполняемый файл в Windows 98 по базовому адресу ниже 0x00400000 загрузчик переместит его на другой адрес. Это увеличит время загрузки приложения, но оно по крайней мере будет выполнено. Если Вы разрабатываете про граммы и для Windows 98, и для Windows 2000, сделайте так, чтобы приложение заг ружалось по базовому адресу не ниже 0x00400000. Функция GetModuleHandle.

HMODULE GetModuleHandle( PCTSTR pszModule);

возвращает описатель/базовый адрес, указывающий, куда именно (в адресном про странстве процесса) загружается EXEили DLL-файл. При вызове этой функции имя нужного EXEили DLL-файла передается как строка с нулевым символом в конце. Если система находит указанный файл, GetModuleHandle возвращает базовый адрес, по которому располагается образ данного файла. Если же файл системой не найден, функция возвращает NULL. Кроме того, можно вызвать эту функцию, передав ей NULL вместо параметра pszModule, — тогда Вы узнаете базовый адрес EXE-файла. Именно это и делает стартовый код из библиотеки С/С++ при вызове (w)WinMain из Вашей программы.