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

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

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

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

Параметры pszApplicationName и pszCommandLine

Эти параметры определяют имя исполняемого файла, которым будет пользоваться новый процесс, и командную строку, передаваемую этому процессу. Нач-

нем с pszCommandLine.

Примечание. Обратите внимание на тип параметра pszCommandLine: PTSTR. Он означает, что CreateProcess ожидает передачи адреса строки, которая не является константой. Дело в том, что CreateProcess в процессе своего выполнения модифицирует переданную командную строку, но перед возвратом управления восстанавливает ее.

Это очень важно: если командная строка содержится в той части образа вашего файла, которая предназначена только для чтения, возникнет ошибка доступа. Например, следующий код приведет к такой ошибке, потому что Visual C++ поместит строку «NOTEPAD» в память только для чтения:

STARTUPINFO si = { sizeof(si) };

 

PROCESS_INFORMATION pi;

 

CreateProcess(NULL, TEXT("NOTEPAD"),

NULL, NULL,

FALSE, 0, NULL, NULL, &si,

&pi);

Когда CreateProcess попытается модифицировать строку, произойдет ошибка доступа. (В прежних версиях Visual C++ эта строка была бы размещена в памяти для чтения и записи, и вызовы CreateProcess не приводили бы к ошибкам доступа.)

Лучший способ решения этой проблемы — перед вызовом CreateProcess копировать константную строку во временный буфер:

STARTUPINFO si = { sizeof(si) };

PROCESS_INFORMATION pi;

TCHAR szCommandLine[] = TEXT("NOTEPAD");

CreateProcess(NULL, szCommandLine, NULL, NULL,

FALSE, 0, NULL, NULL, &si, &pi);

Возможно, вас заинтересуют ключи /Gf и /GF компилятора Visual C++, которые исключают дублирование строк и запрещают их размещение в области только для чтения. (Также обратите внимание на ключ /ZI, который позволяет задействовать отладочную функцию Edit & Continue, поддерживаемую Visual Studio, и подразумевает активизацию ключа /GE) В общем, лучшее, что можете сделать вы, — использовать ключ /GF или создать временный буфер. А еще лучше, если Майкрософт исправит функцию CreateProcess, чтобы та не морочила нам голову. Надеюсь, в следующей версии Windows так и будет.

Кстати, при вызове ANSI-версии CreateProcess в Windows 2000 таких проблем нет, поскольку в этой версии функции командная строка копируется во временный буфер (см. главу 2).

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

Параметр pszCommandLine позволяет указать полную командную строку, используемую функцией CreateProcess при создании нового процесса. Разбирая эту строку, функция полагает, что первый компонент в ней представляет собой имя исполняемого файла, который вы хотите запустить. Если в имени этого файла не указано расширение, она считает его ЕХЕ. Далее функция приступает к поиску заданного файла и делает это в следующем порядке:

1.Каталог, содержащий ЕХЕ-файл вызывающего процесса.

2.Текущий каталог вызывающего процесса.

3.Системный каталог Windows (System32, согласно GetSystemDirectory).

4.Основной каталог Windows.

5.Каталоги, перечисленные в переменной окружения PATH.

Конечно, если в имени файла указан полный путь доступа, система сразу обращается туда и не просматривает эти каталоги. Найдя нужный исполняемый файл, она создает новый процесс и проецирует код и данные исполняемого файла на адресное пространство этого процесса. Затем обращается к процедурам стартового кода из библиотеки C/C++. Тот в свою очередь, как уже говорилось, анализирует командную строку процесса и передает (w)WinMain адрес первого (за именем исполняемого файла) аргумента как pszCmdLine.

Все, о чем я сказал, произойдет, только если параметр pszApplicationName равен NULL (что и бывает в 99% случаев). Вместо NULL можно передать адрес строки с именем исполняемого файла, который надо запустить. Однако тогда придется указать не только его имя, но и расширение, поскольку в этом случае имя не дополняется расширением ЕХЕ автоматически. CreateProcess предполагает, что файл находится в текущем каталоге (если полный путь не задан). Если в текущем каталоге файла нет, функция не станет искать его в других каталогах, и на этом все закончится.

Но даже при указанном в pszApplicationName имени файла CreateProcess все равно передает новому процессу содержимое параметра pszCommandLine как командную строку. Допустим, вы вызвали CreateProcess так:

//размещаем строку пути в области памяти для чтения и записи

TCHAR szPath[] = TEXT("WORDPAD README.TXT");

//порождаем новый процесс

CreateProcess (ТЕХТ("С:\\WINDOWS\\SYSTEM32\\NOTEPAD.ЕХЕ”), SZpATH, …);

Система запускает Notepad, а в его командной строке мы видим «WORDPAD README.TXT». Странно, да? Но так уж она работает, эта функция CreateProcess. Упомянутая возможность, которую обеспечивает параметр pszApplicationName, на самом деле введена в CreateProcess для поддержки подсистемы POSIX в Windows.

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

Параметры psaProcess, psaThread и blnheritHandles

Чтобы создать новый процесс, система должна сначала создать объекты ядра «процесс» и «поток» (для первичного потока процесса). Поскольку это объекты ядра, родительский процесс получает возможность связать с ними атрибуты защиты. Параметры psaProcess и psaThread позволяют определить нужные атрибуты защиты для объектов «процесс» и «поток» соответственно. В эти параметры можно занести NULL, и система закрепит за данными объектами дескрипторы защиты по умолчанию. В качестве альтернативы можно объявить и инициализировать две структуры SECURITY_ ATTRIBUTES; тем самым вы создадите и присвоите объектам «процесс» и «поток» свои атрибуты защиты. Структуры SECURITY_ATTRIBUTES для параметров psaProcess и psaThread используются и для того, чтобы какой-либо из этих двух объектов получил статус наследуемого любым дочерним процессом. (О теории, на которой построено наследование описателей объектов ядра, я рассказывал в главе 3.)

Короткая программа Inherit.cpp демонстрирует, как наследуются описатели объектов ядра. Будем считать, что процесс А порождает процесс В и заносит в параметр psaProcess адрес структуры SECURITYATTRIBUTES, в которой элемент blnheritHandle установлен как TRUE. Одновременно параметр psaThread указывает на другую структуру SECURITY_ATTRIBUTES, в которой значение элемента blnheritHandle — FALSE.

Создавая процесс В, система формирует объекты ядра «процесс» и «поток», а затем — в структуре, на которую указывает параметр ppiProcInfo (о нем поговорим позже), — возвращает их описатели процессу А, и с этого момента тот может манипулировать только что созданными объектами «процесс» и «поток».

Теперь предположим, что процесс А собирается вторично вызвать функцию CreateProcess, чтобы породить процесс С. Сначала ему нужно определить, стоит ли предоставлять процессу С доступ к своим объектам ядра. Для этого используется параметр bInheritHandles. Если он приравнен TRUE, система передает процессу С все наследуемые описатели. В этом случае наследуется и описатель объекта ядра «процесс» процесса В. А вот описатель объекта «первичный поток» процесса В не наследуется ни при каком значении bInheritHandles. Кроме того, если процесс А вызывает CreateProcess, передавая через параметр bInheritHandles значение FALSE, процесс С не наследует никаких описателей, используемых в данный момент процессом А.

Inherit.cpp

/************************************************************ Module name: Inherit.cpp

Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre

************************************************************/

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

#include <Windows.h>

int WINAPI _tWinMain (HINSTANCE hInstanceExe, HINSTANCE, PTSTR pszCmdLine, int nCmdShow) {

// готовим структуру STARTUPINFO для создания процессов

STARTUPINFO si = { sizeof(si) }; SECURITY_ATTRIBUTES saProcess, saThread; PROCESS_INFORMATION piProcessB, piProcessC; TCHAR szPath[MAX_PATH];

//Готовимся к созданию процесса В из процесса А;

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

//должен быть наследуемым.

saProcess.nLength = sizeof(saProcess); saProcess.lpSecurityDescriptor = NULL; saProcess.bInheritHandle = TRUE;

//Описатель, идентифицирующий новый объект "поток",

//НЕ должен быть наследуемым.

saThread.nLength = sizeof(saThread); saThread.lpSecurityDescriptor = NULL; saThread.bInheritHandle = FALSE;

// порождаем процесс В

_tcscpy_s(szPath, _countof(szPath), TEXT("ProcessB")); CreateProcess(NULL, szPath, &saProcess, &saThread,

FALSE, 0, NULL, NULL, &si, &piProcessB);

//структура pi содержит два описателя, относящиеся к процессу А:

//hProcess, который идентифицирует объект "процесс" процесса В

//и является наследуемым, и hThread, который идентифицирует объект

//"первичный поток"процесса В и НЕ является наследуемым

//готовимся создать процесс С из процесса А;

//так как в psaProcess и psaThread передаются NULL, описатели

//объектов "процесс"и "первичный поток" процесса С считаются

//ненаследуемыми поумолчанию

//если процесс А создаст еще один процесс, тот НЕ унаследует

//описатели объектов "процесс" и "первичный поток" процесса С

//поскольку в параметре bInheritHandles передается TRUE,

//процесс С унаследует описатель, идентифицирующий объект

//"процесс"процесса В, но НЕ описатель, идентифицирующий // объект "первичный поток" того же процесса

_tcscpy_s(szPath, _countof(szPath), TEXT("ProcessC"));

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

CreateProcess(NULL, szPath, NULL, NULL,

TRUE, 0, NULL, NULL, &si, &piProcessC);

return(0);

}

Параметр fdwCreate

Параметр fdwCreate определяет флаги, влияющие на то, как именно создается новый процесс. Следующие флаги комбинируются булевым оператором OR:

Флаг DEBUG_PROCESS дает возможность родительскому процессу проводить отладку дочернего, а также всех процессов, которые последним могут быть порождены. Если этот флаг установлен, система уведомляет родительский процесс (он теперь получает статус отладчика) о возникновении определенных событий в любом из дочерних процессов (а они получают статус отлаживаемых).

Флаг DEBUG_ONLY_THIS_PROCESS аналогичен флагу DEBUG_ PROCESS

с тем исключением, что заставляет систему уведомлять родительский процесс о возникновении специфических событий только в одном дочернем процессе — его прямом потомке. Тогда, если дочерний процесс создаст ряд дополнительных, отладчик уже не уведомляется о событиях, «происходящих» в них;

см. также статью «Escape from DLL Hell with Custom Debugging and Instrumentation Tools and Utilities, Part 2» на сайте MSDN (http://msdn.microsoft.com/enus/magazine/cc301686.aspx) — в ней рассказывается об использовании этих флагов для написания отладчика и получения информации в реальном времени о DLL и потоках отлаживаемого приложения.

Флаг CREATE_SUSPENDED позволяет создать процесс и в то же время приостановить его первичный поток. Это позволяет родительскому процессу модифицировать содержимое памяти в адресном пространстве дочернего, изменять приоритет его первичного потока или включать этот процесс в задание (job) до того, как он получит шанс на выполнение. Внеся нужные изменения в дочерний процесс, родительский разрешает выполнение его кода вызовом функции ResumeThread (см. главу 7).

Флаг DETACHEDPROCESS блокирует доступ процессу, инициированному консольной программой, к созданному родительским процессом консольному окну и сообщает системе, что вывод следует перенаправить в новое окно. СUIпроцесс, создаваемый другим СUI-процессом, по умолчанию использует консольное окно родительского процесса. (Вы, очевидно, заметили, что при запуске компилятора С из командного процессора новое консольное окно не создается; весь его вывод «подписывается» в нижнюю часть существующего консольного окна.) Таким образом, этот флаг заставляет новый процесс перенаправлять свой вывод в новое консольное окно вызовом функции AllocConsole.

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

Флаг CREATE_NEW_CONSOLE приводит к созданию нового консольного окна для нового процесса. Имейте в виду, что одновременная установка фла-

гов CREATE_NEW_CONSOLE и DETACHED_PROCESS недопустима.

Флаг CREATE_NO_WINDOW не дает создавать никаких консольных окон для данного приложения и тем самым позволяет исполнять его без пользовательского интерфейса.

Флаг CREATE_NEW_PROCESS_GROUP служит для модификации списка процессов, уведомляемых о нажатии клавиш Ctrl+C и Ctrl+Break. Если в системе одновременно исполняется несколько СUI-процессов, то при нажатии одной из упомянутых комбинаций клавиш система уведомляет об этом только процессы, включенные в группу. Указав этот флаг при создании нового CUIпроцесса, вы создаете и новую группу.

Флаг CREATE_DEFAULT_ERROR_MODE сообщает системе, что новый процесс не должен наследовать режимы обработки ошибок, установленные в родительском (см. раздел, где я рассказывал о функции SetErrorMode).

Флаг CREATE_SEPARATE_WOW_VDM полезен только при запуске 16разрядного Windows-приложения в 32-разрядной Windows. Если он установлен, система создает отдельную виртуальную DOS-машину (Virtual DOSmachine, VDM) и запускает 16-разрядное Windows-приложение именно в ней. (По умолчанию все 16-разрядные Windows-приложения выполняются в одной, общей VDM.) Выполнение приложения в отдельной VDM дает большое преимущество: «рухнув», приложение уничтожит лишь эту VDM, а программы, выполняемые в других VDM, продолжат нормальную работу. Кроме того, 16разрядные Windows-приложения, выполняемые в раздельных VDM, имеют и раздельные очереди ввода. Это значит, что, если одно приложение вдруг «зависнет», приложения в других VDM продолжат прием ввода. Единственный недостаток работы с несколькими VDM в том, что каждая из них требует значительных объемов физической памяти. Windows 98 выполняет все 16разрядные Windows-приложения только в одной VDM, и изменить тут ничего нельзя.

Флаг CREATE_SHARED_WOW_VDM полезен только при запуске 16разрядного Windows-приложения в Windows. По умолчанию все 16-разрядные Windows-приложения выполняются в одной VDM, если только не указан флаг

CREATE_SEPARATE_WOW_VDM. Однако стандартное поведение Windows

2000 можно изменить, присвоив значение «yes» параметру DefaultSepara-

teVDM в разделе HKEY_LOCAL_MACHINE\ System\CurrentControlSet\Control\WOW (После модификации этого параметра систему надо перезагрузить.) Установив значение «yes», но указав флаг

CREATE_SHARED_WOW_VDM, вы вновь заставите Windows 2000 выпол-

нять все 16-разрядные Windows-приложения в од-

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

ной VDM. Заметьте, что приложения могут определять ситуации, когда 32разрядные процессы работают в 64-разряднои ОС, вызовом функции IsWow64Process. Этой функции передается описатель процесса как первый параметр, а также указатель на булево значение которое будет установлено в TRUE, если проверка даст положительный результат.

Флаг CREATE_UNICODE_ENVIRONMENT сообщает системе, что блок переменных окружения дочернего процесса должен содержать Unicodeсимволы. По умолчанию блок формируется на основе ANSI-символов.

Флаг CREATE_FORCEDOS заставляет систему выполнять программу MSDOS, встроенную в 16-разрядное приложение OS/2.

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

Флаг EXTENDED_STARTUPINFO_PRESENT уведомляет ОС о том, что в па-

раметре psiStartInfo передана структура STARTUPINFOEX. Параметр

fdwCreate разрешает задать и класс приоритета процесса.

Однако это необязательно и даже, как правило, не рекомендуется; система присваивает новому процессу класс приоритета по умолчанию. Возможные классы приоритета перечислены в следующей таблице.

Табл. 4-5. Классы приоритета, которые задаются параметром fdwCreate

Класс приоритета

Флаговый идентификатор

Idle (простаивающий)

IDLE_PRIORITY_CLASS

Below normal (ниже обычного)

BELOW_NORMAL_PRIORITY_CLASS

Normal (обычный)

NORMAL_PRIORITY_CLASS

Above normal (выше обычного)

ABOVE_NORMAL_PRIORITY_CLASS

High (высокий)

HIGH_PRIORITY_CLASS

Realtime (реального времени)

REALTIME_PRIORITY_CLASS

Классы приоритета влияют на распределение процессорного времени между процессами и их потоками.

Параметр pvEnvironment

Параметр pvEnvironment указывает на блок памяти, хранящий строки переменных окружения, которыми будет пользоваться новый процесс Обычно вместо этого параметра передается NULL, в результате чего дочерний процесс наследует строки переменных окружения от родительского процесса. В качестве альтернативы можно вызвать функцию GetEnviwnmentStnngs:

PVOID GetEnvironmentStrings();

Она позволяет узнать адрес блока памяти со строками переменных окружения, используемых вызывающим процессом. Полученный адрес можно

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

занести в параметр pvEnvironment функции CreateProcess. (Именно это и делает CreateProcess, если вы передаете ей NULL вместо pvEnvironment.) Если этот блок памяти вам больше не нужен, освободите его, вызвав функцию FreeEnvironmentStrings:

BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock);

Параметр pszCurDir

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

Параметр psiStartInfo

Этот параметр указывает на структуру STARTUPINFO или STARTUPINFOEX:

typedef struct _STARTUPINFO { DWORD cb;

PSTR lpReserved; PSTR lpDesktop; PSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize;

DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFiags;

WORD wShowWindow; WORD cbReserved2; PBYTE lpReserved2; HANDLE hStdlnput; HANDLE hStdOutput; HANDLE hStdError;

} STARTUPINFO, *LPSTARTUPINFO;

typedef struct _STARTUPINFOEX { STARTUPINFO StartupInfo;

struct _PROC_THREAD_ATTRIBUTE_LIST *lpAttributeList; } STARTUPINFOEX, *LPSTARTUPINFOEX;

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

Элементы структуры STARTUPINFO используются Windows-функциями при создании нового процесса. Надо сказать, что большинство приложений порождает процессы с атрибутами по умолчанию. Но и в этом случае вы должны инициализировать все элементы структуры STARTUPINFO хотя бы нулевыми значениями, а в элемент cb — заносить размер этой структуры:

STARTUPINFO si = { sizeof(si) };

CreateProcess(…, &si, …);

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

Когда вам понадобится изменить какие-то элементы структуры, делайте это перед вызовом CreateProcess. Все элементы этой структуры подробно рассматриваются в таблице 4-6. Но заметьте, что некоторые элементы имеют смысл, только если дочернее приложение создает перекрываемое (overlapped) окно, а другие — если это приложение осуществляет ввод-вывод на консоль.

Табл. 4-6. Элементы структур STARTUPINFO и STARTUPINFOEX

Элемент

Окно или

Описание

консоль

 

 

cb

То и другое

Содержит количество байтов, занимаемых структурой

 

 

STARTUPINFO. Служит для контроля версий — на тот слу-

 

 

чай, если Майкрософт расширит эту структуру в будущем.

 

 

Программа должна инициализировать cb как sizeof

 

 

(STARTUPINFO) sizeof (STARTUPINFOEX)

lpReserved

То и другое

Зарезервирован. Инициализируйте как NULL

lpDesktop

То и другое

Идентифицирует имя рабочего стола, на котором запускается

 

 

приложение. Если указанный рабочий стол существует, но-

 

 

вый процесс сразу же связывается с ним. В ином случае сис-

 

 

тема сначала создает рабочий стол с атрибутами по умолча-

 

 

нию, присваивает ему имя, указанное в данном элементе

 

 

структуры, и связывает его с новым процессом. Если lpDesk-

 

 

top равен NULL (что чаще всего и бывает), процесс связыва-

 

 

ется с текущим рабочим столом

lpTitle

Консоль

Определяет заголовок консольного окна. Если lpTitle

 

 

NULL, в заголовок выводится имя исполняемого файла

 

 

 

 

 

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

Табл. 4-6. (окончание)

 

 

 

 

 

Элемент

Окно или

 

 

Описание

 

консоль

 

 

 

 

 

 

 

 

 

dwX

То и другое

Указывают х- и у-координаты (в пикселах) окна приложе-

dwY

 

ния. Эти координаты используются, только если дочерний

 

 

процесс создает свое первое перекрываемое окно с иден-

 

 

тификатором CW_USE-DEFAULT в параметре х функции

 

 

CreateWindow.

 

 

 

 

 

В приложениях, создающих консольные окна, данные эле-

 

 

менты определяют верхний левый угол кон сольного окна

dwXSize

То и другое

Определяют ширину и высоту (в пикселах) окна приложе-

dwYSize

 

ния. Эти значения используются, только если дочерний

 

 

процесс создает свое первое перекрываемое окно с иден-

 

 

тификатором CWUSEDE-FAULT в параметре nWidth

 

 

функции CreateWindow.

 

 

 

 

В приложениях, создающих консольные окна, данные эле-

 

 

менты определяют ширину и высоту кон сольного окна.

dwXCountC

Консоль

Определяют ширину и высоту (в символах) консольных

hars

 

окон дочернего процесса.

 

 

dwYCountC

 

 

 

 

 

 

hars

 

 

 

 

 

 

dwFillAttri

Консоль

Задает цвет текста и фона в консольных окнах дочернего

bute

 

процесса.

 

 

 

 

dwFlags

То и другое

См. ниже и следующую таблицу.

 

wShowWin

Окно

Определяет, как именно должно выглядеть первое пере-

dow

 

крываемое окно дочернего процесса. При первом вызове

 

 

функции ShowWindow в качестве параметра вместо

 

 

nCmdShow передается значение wShowWindow. При после-

 

 

дующих вызовах ShowWindow значение wShowWindow пе-

 

 

редается

только

при

передаче

идентификатора

 

 

SWSHOWDEFAULT. Чтобы параметр wShowWindow во-

 

 

зымел действие, в dwFlags должен быть установлен флаг

 

 

STARTF USESHOWWINDOW

 

cbReserved

То и другое

Зарезервирован. Инициализируйте как 0.

 

2

 

 

 

 

 

 

lpReserved

То и другое

Зарезервирован. Инициализируйте как NULL. Значения

2

 

cbReserved2 и lpReserved2 используются библиотекой С

 

 

для передачи информации при использовании _dospawn

 

 

для запуска приложения. Реализацию можно увидеть в

 

 

файлах dospawn.c and ioinitx, расположенных в папке

 

 

VC\crt\src\ внутри каталога Visual Studio

 

hStdInput

Консоль

Определяют описатели буферов для консольного ввода-

hStdOutput

 

вывода. По умолчанию hStdInput идентифицирует буфер

hStdError

 

клавиатуры, a hStdOutput и hStdError— буфер консольного

 

 

окна

 

 

 

 

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