Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6265
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

Глава 3. Объекты ядра.docx

47

//его описатель разрешает доступ как для чтения, так и для записи

HANDLE hFileMapRW = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 10240, NULL);

//создаем другой описатель на тот же объект;

//этот описатель разрешает доступ только для чтения

HANDLE hFileMapR0;

DuplicateHandle(GetCurrentProcess(), hFileMapRW, GetCurrentProcess(),

&hFileMapR0, FILE_MAP_READ, FALSE, 0);

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

ReadFromTheFileMapping(hFileMapRO);

//закрываем объект "проекция файла", доступный только для чтения

CloseHandle(hFileMapRO);

//проекция файла нам по-прежнему полностью доступна через hFileMapRW

// если проекция файла больше не нужна основному коду, закрываем ее

CloseHandle(hFileMapRW);

}

Ч А С Т Ь I I

ПРИСТУПАЕМ К РАБОТЕ

Г Л А В А 4

Процессы

Эта глава о том, как система управляет выполняемыми приложениями. Сначала я определю понятие «процесс» и объясню, как система создает объект ядра «процесс». Затем я покажу, как управлять процессом, используя сопоставленный с ним объект ядра. Далее мы обсудим атрибуты (или свойства) процесса и поговорим о нескольких функциях, позволяющих обращаться к этим свойствам и изменять их. Я расскажу также о функциях, которые создают (порождают) в системе дополнительные процессы. Ну и, конечно, описание процессов было бы неполным, если бы я не рассмотрел механизм их завершения. Итак, приступим.

Процесс обычно определяют как экземпляр выполняемой программы, и он состоит из двух компонентов:

объекта ядра, через который операционная система управляет процессом. Там же хранится статистическая информация о процессе;

адресного пространства, в котором содержится код и данные всех ЕХЕ- и DLL-модулей. Именно в нем находятся области памяти, динамически распре-

деляемой для стеков потоков и других нужд.

Процессы инертны. Чтобы процесс что-нибудь выполнил, в нем нужно создать поток. Именно потоки отвечают за исполнение кода, содержащегося в адресном пространстве процесса. В принципе, один процесс может владеть несколькими потоками, и тогда они «одновременно» исполняют код в адресном пространстве процесса. Для этого каждый поток должен располагать собственным набором регистров процессора и собственным стеком. В каждом процессе есть минимум один поток. При создании процесса система автоматически создает его первый поток, называемый главным потоком. Если бы у процесса не было ни одного потока, ему нечего было бы делать на этом свете, и система автоматически уничтожила бы его вместе с выделенным ему адресным пространством.

Чтобы все эти потоки работали, операционная система отводит каждому из них определенное процессорное время. Выделяя потокам отрезки време-

Глава 4. Процессы.docx 77

ни (называемые квантами) по принципу карусели, она создает тем самым иллюзию одновременного выполнения потоков. Рис. 4-1 иллюстрирует распределение процессорного времени между потоками на машине с одним процессором.

Рис. 4-1. Операционная система выделяет потокам кванты времени по принципу карусели

Если в машине установлено более одного процессора, алгоритм работы операционной системы значительно усложняется (в этом случае система стремится сбалансировать нагрузку между процессорами). Ядро Windows автоматически управляет планированием исполнения потоков. Для использования преимуществ многопроцессорности в своих программах разработчикам ничего не требуется делать — система автоматически делает все необходимое для этого. Однако в ваших силах оптимизировать алгоритм вашего приложения, чтобы оно лучше использовало ресурсы многопроцессорных компьютеров.

Ваше первое Windows-приложение

Windows поддерживает два типа приложений: основанные на графичес-

ком интерфейсе (graphical user interface, GUI) и консольные (console user interface, GUI). У приложений первого типа внешний интерфейс чисто графический. GUI-приложения создают окна, имеют меню, взаимодействуют с пользователем через диалоговые окна и вообще пользуются всей стандартной «Windows'oвской» начинкой. Почти все стандартные программы

Windows — Notepad, Calculator, WordPad и др. — являются GUI-приложени-

ями. Приложения консольного типа работают в текстовом режиме: они не формируют окна, не обрабатывают сообщения и не требуют GUI. И хотя кон-

78 Часть II. Приступаем к работе

сольные приложения на экране тоже размещаются в окне в нем содержится только текст. Командные процессоры вроде Cmd.exe (в Windows 2000) или Command.com (в Windows 98)- типичные образцы подобных приложений.

Вместе с тем граница между двумя типами приложении весьма условна. Можно, например, создать консольное приложение, способное отображать диалоговые окна. Скажем, в командном процессоре вполне может быть специальная команда, открывающая графическое диалоговое окно со списком команд; вроде мелочь — а избавляет от запоминания лишней информации. В то же время можно создать и GUI-приложение, выводящее текстовые строки в консольное окно. Я сам часто писал такие программы: создав консольное окно, я пересылал в него отладочную информацию, связанную с исполняемым приложением. Но, конечно, графический интерфейс предпочтительнее, чем старомодный текстовый. Как показывает опыт, приложения на основе GUI «дружественнее» к пользователю, а значит и более популярны.

Когда вы создаете проект приложения, Microsoft Visual C++ устанавливает такие ключи для компоновщика, чтобы в исполняемом файле был указан соответствующий тип подсистемы. Для GUI-программ используется ключ

/SUBSYSTEM:CONSOLE, а для GUI-приложений — /SUBSYSTEM:WINDOWS.

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

Во всех Windows-приложениях должна быть входная функция, за реализацию которой отвечаете вы. Существует две такие функции:

int WINAPI _tWinMain( HINSTANCE hlnstanceExe, HINSTANCE,

PTSTR pszCmdLine, int nCmdShow);

int _tmain( int argc,

TCHAR *argv[], TCHAR *envp[]);

Конкретная функция зависит от того, используете ли вы Unicode или нет. На самом деле входная функция операционной системой не вызывается. Вместо этого происходит обращение к стартовой функции из библиотеки C/C++, заданной во

время компоновки параметром

-entry: командной строки. Она инициализирует

библиотеку

C/C++,

чтобы

можно

было

вызывать

Глава 4. Процессы.docx 79

такие функции, как malloc и free, а также обеспечивает корректное создание любых объявленных вами глобальных и статических С++-объектов до того, как начнется выполнение вашего кода. В следующей таблице показано, в каких случаях реализуются те или иные входные функции.

Табл. 4-1. Типы приложений и соответствующие им входные функции

 

 

Стартовая функция,

Тип приложения

Входная функция

встраиваемая в ваш испол-

 

 

няемый файл

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

_tWinMain (WinMain)

WinMainCRTStartup

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

 

 

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

_tWinMain (wWinMain)

wWinMainCRTStartup

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

 

 

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

_tmain (main)

mainCRTStartup

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

 

 

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

_tmain (wmain)

wmainCRTStartup

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

 

 

Компоновщик отвечает за выбор подходящей стартовой функции из библиотеки C/C++ при компоновке исполняемого файла. Если зада ключ /SUBSYSTEM:WINDOWS, компоновщик ищет в коде функцию WinMain или wWinMain. Если этих функций нет, компоновщик сообщает об ошибке «unresolved external symbol». В противном случае компоновщик выбирает WinMainCRTStartup или wWinMainCRTStartup, соответственно.

Аналогичным образом, если задан ключ /SUBSYSTEM:CONSOLE, компоновщик ищет в коде функцию main или wmain и выбирает соответственно mainCRTStartup или wmainCRTStartup; если в коде нет ни main, ни wmain, сооб-

щается о той же ошибке — «unresolved external symbol».

Но не многие знают, что в проекте можно вообще не указывать ключ / SUBSYSTEM компоновщика. Если вы так и сделаете, компоновщик будет сам определять подсистему для вашего приложения. При компоновке он проверит, какая из четырех функций (WinMain, wWinMain, main или wmain) присутствует в вашем коде, и на основании этого выберет подсистему и стартовую функцию из библио-

теки C/C++.

Одна из частых ошибок, допускаемых теми, кто лишь начинает работать с Visual C++, — выбор неверного типа проекта. Например, разработчик хочет создать проект Win32 Application, а сам включает в код функцию main. При его сборке он получает сообщение об ошибке, так как для проекта Win32 Application в командной строке компоновщика автоматически указывается ключ /SUBSYSTEM:WINDOWS, который требует присутствия в коде функции WinMain или wWinMain. В этот момент разработчик может выбрать один из четырех вариантов дальнейших действий:

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

80 Часть II. Приступаем к работе

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

открыть вкладку Link в диалоговом окне Project Settings и заменить ключ I /SUBSYSTEM:WINDOWS на /SUBSYSTEM:CONSOLE в Configuration Properties/Linker/System/SubSystem (рис. 4-2). Некоторые думают, что это единствен-

ный вариант;

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

или Win32 Console Application.

Рис. 4-2. Выбор подсистемы CUI в окне свойств проекта

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

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

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

Глава 4. Процессы.docx 81

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

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

вызывают конструкторы всех глобальных и статических объектов С++-классов. Закончив эти операции, стартовая функция обращается к входной функции в

вашей программе. Если вы написали ее в виде _tWinMain и задали параметр _UNICODE, то она вызывается так:

GetStartupInfo(&StartupInfo);

int nMainRetVal = wWinMain((HINSTANCE)&__ImageBase, NULL, pszCommandLineUnicode, (StartupInfo.dwFlags & STARTF_USESHOWWINDOW)

? StartupInfo.wShowWindow : SW_SHOWDEFAULT);

Если же параметр _UNICODE не задан, входная функция вызывается так:

GetStartupInfo(&StartupInfo);

int nMainRetVal = WinMain((HINSTANCE)&__ImageBase, NULL, pszCommandLineAnsi, (StartupInfo.dwFlags & STARTF_USESHOWWINDOW)

? StartupInfo.wShowWindow : SW_SHOWDEFAULT);

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

Функция _tmain вызывается так, если задан параметр _UNICODE:

int nMainRetVal = wmain(argc, argv, envp);

либо так, если этот параметр не задан:

int nMainRetVal = main(argc, argv, envp);

Обратите внимание, что при генерации приложений с использованием мастеров Visual Studio третий параметр (блок переменных окружения) во входной функции не определяется:

int _tmain(int argc, TCHAR* argv[]);

Если вам нужен доступ к переменным окружения процесса, просто замените предыдущую строку кода следующей:

int _tmain(int argc, TCHAR* argv[], TCHAR* env[])

Параметр env — это указатель на массив, содержащий переменные окружения с их значениями, разделенные знаком равенства (=), подробнее об этом см. ниже в разделе, посвященном переменным окружения.

Соседние файлы в предмете Программирование на C++