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

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

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

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

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

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

Увы, многим пользователям не под силу разобраться в переменных окружения, а значит, трудно указать правильные значения. Ведь для этого надо не только хорошо знать синтаксис переменных, но и, конечно, понимать, что стоит за теми или иными их значениями. С другой стороны, почти все (а может, и все) приложения, основанные на GUI, дают возможность тонкой настройки через диалоговые окна. Такой подход, естественно, нагляднее и проще.

А теперь, если у вас еще не пропало желание манипулировать переменными окружения, поговорим о предназначенных для этой цели функциях. GetEnvironmentVariable позволяет выявлять присутствие той или иной переменной окружения и определять ее значение:

DWORD GetEnvironmentVariable(

PCTSTR pszName,

PTSTR pszValue,

DWORD cchValue);

При вызове GetEnvironmentVariable параметр pszName должен указывать на имя интересующей вас переменной, pszValue — на буфер, в который будет помещено значение переменной, а в cchValue следует сообщить размер буфера в символах. Функция возвращает либо количество символов, скопированных в буфер, либо 0, если ей не удалось обнаружить переменную окружения с таким именем. Однако размер значения переменной окружения заранее не известен, поэтому при передаче в параметре cchValue значения 0 функция GetEnvironmentVariable возвращает размер (число символов плюс завершающий NULL-символ). Вот пример безопасного использования этой функции:

void PrintEnvironmentvariable(PCTSTR pszVariableName) {

PTSTR pszValue = NULL;

// получаем размер буфера, необходимого для хранения значения

DWORD dwResult = GetEnvironmentVariable(pszVariableName, pszValue, 0); if (dwResult != 0) {

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

// выделяем буфер для значения переменной окружения

DWORD size = dwResult * sizeof(TCHAR); pszValue = (PTSTR)malloc(size);

GetEnvironmentVariable(pszVariableName, pszValue, size); _tprintf(TEXT("%s=%s\n"), pszVariableName, pszValue); free(pszValue);

} else {

_tprintf(TEXT(",%s,=<unknown value>\n"), pszVariableName);

}

}

Кстати, в реестре многие строки содержат подставляемые части, например:

%USERPROFILE%\Documents

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

C:\Users\jrichter

После подстановки переменной в строку реестра получим:

С:\Users\jrichter\Documents

Поскольку такие подстановки делаются очень часто, в Windows есть функция

ExpandEnvironmentStrings:

DWORD ExpandEnvironmentStrings(

PTCSTR pszSrc,

PTSTR pszDst,

DWORD chSize);

Параметр pszSrc принимает адрес строки, содержащей подставляемые части, а параметр pszDst — адрес буфера, в который записывается развернутая строка. Параметр chSize определяет максимальный размер буфера в символах. Возвращает функция размер буфера (в символах), необходимого для хранения результата подстановки. Если значение параметра chSize меньше этого значения, вместо подстановки переменные %% заменяются пустыми строками. Поэтому ExpandEnvironmentStrings обычно вызывают дважды, как показано в примере ниже:

DWORD chValue =

ExpandEnvironmentStrings(TEXT(“PATH=,„%PATH%‟”), NULL, 0); PTSTR pszBuffer = new TCHAR[chValue];

chValue = ExpandEnvironmentStrings(TEXT("PATH='%PATH%'"), pszBuffer, chValue); _tprintf(TEXT(“%s\r\n”), pszBuffer);

deleted pszBuffer;

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

Наконец, функция SetEnvironmentVanable позволяет добавлять, удалять и модифицировать значение переменной:

BOOL SetEnvironmentVariable(

PCTSTR pszName,

PCTSTR pszValue);

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

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

Привязка к процессорам

Обычно потоки внутри процесса могут выполняться на любом процессоре компьютера. Однако их можно закрепить за определенным подмножеством процессоров из числа имеющихся на компьютере. Это свойство называется привязкой к процессорам (processor affinity) и подробно обсуждается в главе 7. Дочерние процессы наследуют привязку к процессорам от родительских.

Режим обработки ошибок

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

UINT SetErrorMode(UINT fuErrorMode);

Параметр fuErrorMode — это набор флагов (таблица 4-3), комбинируемых побитовой операцией OR.

Табл. 4-3. Флаги SetErrorMode

Флаг

Описание

SEM_FAILCRITICALERRORS

Система не выводит окно с сообщением от

 

обработчика критических ошибок и возвра-

 

щает ошибку в вызывающий процесс

SEM_NOGPFAULTERRORBOX

Система не выводит окно с сообщением о на-

 

рушении общей защиты; этим флагом мани-

 

пулируют только отладчики, самостоятельно

 

обрабатывающие нарушения общей защиты с

 

помощью обработчика исключений

SEM_NOOPENFILEERRORBOX

Система не выводит окно с сообщением об

 

отсутствии искомого файла

 

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

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

 

Флаг

Описание

SEM_NOALIGNMENTFAULTEXCEPT

Система автоматически исправляет на-

 

рушения в выравнивании данных, и они

 

становятся невидимы приложению; этот

 

флаг не действует на процессорах х86

По умолчанию дочерний процесс наследует от родительского флаги, указывающие на режим обработки ошибок. Иначе говоря, если у процесса в данный момент установлен флаг SEM_NOGPFAULTERRORBOX и он порождает другой процесс, этот флаг будет установлен и у дочернего процесса. Однако «наследник» об этом не уведомляется, и он вообще может быть не рассчитан на обработку ошибок такого типа (в данном случае — нарушений общей защиты). В результате, если в одном из потоков дочернего процесса все-таки произойдет подобная ошибка, этот процесс может завершиться, ничего не сообщив пользователю. Но родительский процесс способен предотвратить наследование дочерним процессом своего режима обработки ошибок, указав при вызове функции CreateProcess флаг

CREATE_DEFAULT_ERROR_MODE (о CreateProcess чуть позже).

Текущие диск и каталог для процесса

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

Поток может получать и устанавливать текущие каталог и диск для процесса с помощью двух функций:

DWORD GetCuгrentDirectory(

DWORD cchCurDir,

PTSTR pszCurDir);

BOOL SetCurrentDirectory(PCTSTR pszCurDir);

Если предоставленный буфер недостаточно велик, GetCurrentDirectory возвращает размер буфера (в символах), необходимого для хранения пути к этой папке (включая концевую строку «/0»), ничего не копируя в предоставленный буфер; в этом случае его можно установить в NULL. Если вызов успешен, функция возвращает длину строки (в символах, без учета концевой строки «/0»).

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

Примечание. В WinDef.h значение константы МАХ_РАТН установлено в 260, это максимальная длина имени файла или каталога. Таким образом, при вызове GetCurrentDirectory можно смело передавать буфер, содержащий МАХ_РАТН элементов типа TCHAR.

Текущие каталоги для процесса

Система отслеживает текущие диск и каталог для процесса, но не текущие каталоги на каждом диске. Однако в операционной системе предусмотрен кое-какой сервис для манипуляций с текущими каталогами на разных дисках. Он реализуется через переменные окружения конкретного процесса. Например:

=C:=C:\Utility\Bin =D:=D:\Program Files

Эти переменные указывают, что текущим каталогом на диске С является

\Utility\Bin, а на диске D — Program Files.

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

Скажем, если текущий каталог для процесса — C:\Utility\Bin и вы вызываете функцию CreateFile, чтобы открыть файл D:\ReadMe.txt, система ищет переменную =D:. Поскольку переменная =D: существует, система пытается открыть файл ReadMe.txt в каталоге D:\Program Files. А если бы таковой переменной не было, система искала бы файл ReadMe.txt в корневом каталоге диска D. Кстати, файловые Windows-функции никогда не добавляют и не изменяют переменные окружения, связанные с именами дисков, а лишь считывают их значения.

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

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

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

DWORD GetFullPathName(

PCTSTR pszFile,

DWORD cchPath,

PTSTR pszPath,

PTSTR *ppszFilePart);

Например, чтобы получить текущий каталог на диске С, функцию вызывают так:

TCHAR szCurDir[MAX_PATH];

DWORD cchLength = GetFullPathName(TEXT(“C:"), MAX_PATH, szCurDir, NULL);

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

Определение версии системы

Весьма часто приложению требуется определять, в какой версии Windows оно выполняется. Причин тому несколько. Например, программа может использовать функцию CreateFileTransacted из Windows API, но в полной мере эта функция реализованы лишь в Windows Vista.

Насколько я помню, функция GetVersion есть в API всех версий Windows:

DWORD GetVersion();

С этой простой функцией связана целая история. Сначала ее разработали для 16-разрядной Windows, и она должна была в старшем слове возвращать номер версии MS-DOS, а в младшем — номер версии Windows. Соответственно в каждом слове старший байт сообщал основной номер версии, младший — дополнительный номер версии.

Увы, программист, писавший ее код, слегка ошибся, и получилось так, что номера версии Windows поменялись местами: в старший байт попадал дополнительный номер, а в младший — основной. Поскольку многие программисты уже начали пользоваться этой функцией, Майкрософт пришлось оставить все, как есть, и изменить документацию с учетом ошибки.

Из-за всей этой неразберихи вокруг GetVersion в Windows API включили но-

вую функцию — GetVersionEx:

BOOL GetVersionEx(POSVERSIONINFOEX pVersionInformation);

Перед обращением к GetVersionEx программа должна создать структуру OSVERSIONINFOEX, показанную ниже, и передать ее адрес этой функции. В

typedef struct {

DWORD dwOSVersionlnfoSize; DWORD dwMajorVersion; DWORD dwMinorVersion;

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

DWORD

dwBuildNumber;

DWORD

dwPlatformId;

TCHAR

szCSDVersion[128];

WORD

wServicePackMajor;

WORD

wServicePackMinor;

WORD

wSuiteMask;

BYTE

wProductType;

BYTE

wReserved;

} OSVERSIONINFOEX, *P0SVERSI0NINF0EX;

Эта структура появилась в Windows 2000. В остальных версиях Windows используется структура OSVERSIONINFO, в которой нет последних пяти элементов, присутствующих в структуре OSVERSIONINFOEX.

Обратите внимание, что каждому компоненту номера версии операционной системы соответствует свой элемент структуры. Это сделано специально — чтобы программисты не возились с выборкой данных из всяких там старшихмладших байтов-слов (и не путались в них!); теперь программе гораздо проще сравнивать ожидаемый номер версии операционной системы с действительным. Назначение каждого элемента структуры OSVERSIONINFOEX описано в таблице 4-4. Анализ полей этой структуры подробно разбирается в статье «Getting the System Version» на веб-сайте MSDN (http://msdn2.microsoft.com/engb/library/ms724429.aspx).

В Windows 2000 появилась новая функция, VerifyVersionInfo, которая сравнивает версию установленной операционной системы с тем, что требует ваше приложение:

B00L VerifyVersionInfo(

P0SVERSI0NINF0EX pVersionInformation,

DWORD dwTypeMask,

DW0RDL0NG dwlConditionMask);

Табл. 4-4. Члены структуры OSVERSIONINFOEX

Элемент

 

Описание

 

dwOSVersionInfoSize

Размер структуры; перед обращением к функции GetVer-

 

sionEx должен быть заполнен вызовом sizeof (OSVERSIO-

 

NINFO) или sizeof (OSVERSIONINFOEX)

 

dwMajorVersion

Основной номер версии операционной системы

 

dwMinorVersion

Дополнительный номер версии операционной системы

dwBuildNumber

Версия сборки данной системы

 

dwPlatformId

Идентификатор платформы, поддерживаемой данной сис-

 

темой;

его

возможные

значения:

 

VER_PLATFORM_WIN32s

(Win32-платформы),

 

VER_PLATFORM_WIN32_WINDOWS (Windows 95/98),

 

VER_PLATFORM_WIN32_NT (Windows NT/2000/XP,

 

Windows Server 2003, Windows Vista)

 

 

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

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

Элемент

Описание

szCSDVersion

Этот элемент содержит текст — дополнительную информа-

 

цию об установленной операционной системе

wServicePackMajor

Основной номер версии последнего установленного пакета

 

исправлений (service pack)

wServicePackMinor

Дополнительный номер версии последнего установленного

 

пакета исправлений

wSuiteMask

Сообщает, какие программные пакеты (suites) доступны в

 

системе; его возможные значения:

 

VER_SUITE_SMALLBUSINESS, VER_SUITE ENTERPRISE,

 

VER_SUITE_BACKOFFICE, VER_SUITE_ COMMUNICA-

 

TIONS, VER_SUITE_TERMINAL, VER_SUITE_ SMALLBU-

 

SINESS_RESTRICTED, VER_SUITE_EMBEDDEDNT,

 

VER_SUITE_DATACENTER, VER_SUITE_SINGLEUSERTS

 

(один сеанс служб терминалов в расчете на пользователя),

 

VER_SUITE_PERSONAL (позволяет различить редакции

 

Ноmе и Professional), VER_SUITE_BLADE, VER_SUITE_

 

EMBEDDED_ESTRICTED, VER_SUITE_SECURITY_ AP-

 

PLIANCE, VER_SUITE_STORAGE_SERVER, VER_SUITE_

 

COMPUTE_SERVER)

wProductType

Сообщает, какой именно вариант операционной системы ус-

 

тановлен; его возможные значения:

 

VER_NT_WORKSTATION, VER_NT_SERVER,

 

VER_NT_DOMAIN_CONTROLLER

wReserved

Зарезервирован на будущее

Чтобы использовать эту функцию, создайте структуру OSVERSIONINFOEX, запишите в ее элемент dwOSVersionInfoSize размер структуры, а потом инициализируйте любые другие элементы, важные для вашей программы. При вызове VerifyVersionInfo параметр dwTypeMask указывает, какие элементы структуры вы инициализировали. Этот параметр принимает любые комбинации следующих флагов: VER_MINORVERSION, VER_MAJORVERSION, VER_BUILDNUMBER, VER_PLATFORMID, VER_SERVICEPACKMINOR, VER_SERVICEPACKMAJOR, VER_SUITENAME и VER_PRODUCT_TYPE. По-

следний параметр, dwlConditionMask, является 64-разрядным значением, которое управляет тем, как именно функция сравнивает информацию о версии системы с нужными вам данными.

Параметр dwlConditionMask устанавливает правила сравнения через сложный набор битовых комбинаций. Для создания требуемой комбинации используйте макрос VER_SET_CONDITION:

VER_SET_C0NDITI0N(

DWORDLONG dwlConditionMask,

ULONG dwTypeBitMask,

ULONG dwConditionMask)

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

Первый параметр, dwlConditionMask, идентифицирует переменную, битами которой вы манипулируете. Вы не передаете адрес этой переменной, потому что VER_SET_CONDITION — макрос, а не функция. Параметр dwTypeBitMask указывает один элемент в структуре OSVERSIONINFOEX, который вы хотите сравнить со своими данными. (Для сравнения нескольких элементов придется обращаться к VER_SET_CONDITION несколько раз подряд.) Флаги, передаваемые в этом параметре (VER_MINORVERSION, VER_BUILDNUMBER и пр.), идентич-

ны передаваемым в параметре dwTypeMask функции Verify VersionInfо. Последний параметр макроса VER_SET_CONDITION, dwConditionMask, со-

общает, как вы хотите проводить сравнение. Он принимает одно из следующих значений: VER_EQUAL, VER_GREATER, VER_GREATER_EQUAL, VER_LESS

или VER_LESS_EQUAL Вы можете использовать эти значения в сравнениях по

VER_PRODUCT_TYPE. Например, значение VER_NT_WORKSTATION меньше,

чем VER_NT_SERVER. Но в сравнениях по VER_SUITENAME вместо этих значений применяется VER_AND (должны быть установлены все программные пакеты) или VER_OR (должен быть установлен хотя бы один из программных пакетов).

Подготовив набор условий, вы вызываете VerifyVersionInfo и получаете ненулевое значение, если система отвечает требованиям вашего приложения, или 0, если она не удовлетворяет этим требованиям или если вы неправильно вызвали функцию. Чтобы определить, почему VerifyVersionInfo вернула 0, вызовите GetLastError. Если та вернет ERROR_OLD_WIN_VERSION, значит, вы правильно вызвали функцию VerifyVersionInfo, но система не соответствует предъявленным требованиям.

Вот как проверить, установлена ли Windows Vista:

//Готовим структуру OSVERSIONINFOEX, сообщая,

//что нам нужна Windows Vista. OSVERSIONINFOEX osver = { 0 }; osver.dwOSVersionlnfoSize = sizeof(osver); osver.dwMajorVersion = 6; osver.dwMinorVersion = 0; osver.dwPlatformId = VER_PLATF0RM_WIN32_NT;

//формируем маску условий

DWORDLONG dwlConditionMask = 0; // You MUST initialize this to 0. VER_SET_C0NDITI0N(dwlConditionMask, VER_MAJ0RVERSI0N, VER_EQUAL); VER_SET_CONDITION(dwlConditionMask, VER_MIN0RVERSI0N, VER_EQUAL); VER_SET_C0NDITI0N(dwlConditionMask, VER_PLATFORMID, VER_EQUAL);

// проверяем версию

if (VerifyVersionInfo(&osver, VER_MAJORVERSION | VER_MIN0RVERSI0N | VER_PLATF0RMID, dwlConditionMask)) {

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

//хост-система точно соответствует Windows Vista

}else {

//хост-система не является Windows Vista

}

Функция CreateProcess

Процесс создается при вызове вашим приложением функции CreateProcess:

BOOL CreateProcess(

PCTSTR pszApplicationName,

PTSTR pszCommandLine,

PSECURITY_ATTRIBUTES psaProcess,

PSECURITY.ATTRIBUTES psaThread,

BOOL bInheritHandles,

DWORD fdwCreate,

PVOID pvEnvironment,

PCTSTR pszCurDir,

PSTARTUPINFO psiStartInfo,

PPROCESS_INFORMATION ppiProcInfo);

Когда поток в приложении вызывает CreateProcess, система создает объект ядра «процесс» с начальным значением счетчика числа его пользователей, равным 1. Этот объект — не сам процесс, а компактная структура данных, через которую операционная система управляет процессом. (Объект ядра «процесс» следует рассматривать как структуру данных со статистической информацией о процессе.) Затем система создает для нового процесса виртуальное адресное пространство и загружает в него код и данные как для исполняемого файла, так и для любых DLL (если таковые требуются).

Далее система формирует объект ядра «поток» (со счетчиком, равным 1) для первичного потока нового процесса. Как и в первом случае, объект ядра «поток» — это компактная структура данных, через которую система управляет потоком. Первичный поток начинает с исполнения стартового кода из библиотеки C/C++, который, в конечном счете, вызывает функцию WinMain, wWinMain, main или wmain в вашей программе. Если системе удастся создать новый процесс и его первичный поток, CreateProcess вернет TRUE.

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

На этом мы закончим общее описание и перейдем к подробному рассмотрению параметров функции CreateProcess.

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