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

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

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

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

Заметьте, что теперь эти переменные (табл. 4-2) не рекомендуется использовать по соображениям безопасности, поскольку использующий их код может быть запущен прежде, чем они будут инициализированы библиотекой С. В силу этих причин лучше напрямую вызывать соответствующие функции Windows API.

Когда ваша входная функция возвращает управление, стартовая обращается к функции exit библиотеки C/C++ и передает ей значение nMainRetVal. Функция exit выполняет следующие операции:

вызывает все функции, зарегистрированные вызовами функции

_onexit;

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

в отладочных сборках генерирует вызовом _CrtDumpMemoryLeaks список утечек памяти, управляемой средствами библиотеки C/C++

(если установлен флаг _CRTDBG_LEAK_CHECK_DF);

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

Табл. 4-2. Глобальные переменные библиотеки C/C++, доступные вашим программам

Имя перемен-

Тип

Описание

ной

 

 

_osver

unsigned int

Версия сборки операционной системы. Например,

 

 

у Windows Vista RTM этот номер был 6000, соот-

 

 

ветственно _osver равна 6000. Заменяется функци-

 

 

ей GetVersionEx

_winmajor

unsigned int

Основной номер версии Windows в шестнадцате-

 

 

ричной форме. Для Windows Vista это значение

 

 

равно 6. Заменяется функцией GetVersionEx

_winminor

unsigned int

Дополнительный номер версии Windows в шест-

 

 

надцатеричной форме. Для Windows Vista это зна-

 

 

чение равно 0. Заменяется функцией GetVersionEx

_winver

unsigned int

Вычисляется как (_winmajor << 8) + _winminor.

 

 

Заменяется функцией GetVersionEx

__argc

unsigned int

Количество аргументов, переданных в командной

 

 

строке. Заменяется функцией GetCommandLine

__argv

char.

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

__wargv

wchar_t.

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

 

 

зывает на один из аргументов командной строки.

 

 

Заметьте, что __argv = NULL, если установлен

 

 

флаг _UNICODE и __wargv = NULL в противном

 

 

случае. Заменяется функцией GetCommandLine

 

 

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

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

 

Имя перемен-

Тип

Описание

ной

 

 

_environ

char

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

_wenviron

wchar_t

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

 

 

переменную окружения. Заметьте, что __wenviron

 

 

= NULL, если не установлен флаг _UNICODE, и

 

 

__environ = NULL в противном случае. Заменяется

 

 

функцией GetEnvironmentVariable

_pgmptr

char

Полный путь и имя (в ANSI или Unicode) запус-

_wpgmptr

wchar_t

каемой программы. Заметьте, что __pgmptr =

 

 

NULL, если установлен флаг _UNICODE.

 

 

Заменяется вызовом функции GetModuleFileName

 

 

с передачей NULL в первом параметре

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

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

HICON LoadIcon(

HINSTANCE hInstance,

PCTSTR pszIcon);

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

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

DWORD GetModuleFileName(

HMODULE hlnstModule,

PTSTR pszPath,

DWORD cchPath);

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

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

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

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

Функция GetModuleHandle:

HM0DULE GetModuleHandle(PCTSTR pszModule);

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

Другой способ — вызов GetModuleHandleEx с передачей GET_MODULE_ HANDLE_EX_FLAG_FROM_ADDRESS в качестве первого параметра и адреса текущего метода - в качестве второго параметра. В указатель на HMODULE, переданный как последний параметр, будет записан базовый адрес DLL, содержащей переданную функцию. Вот примеры, иллюстрирующие оба способа:

extern "С" const IMAGE_D0S_HEADER __ImageBase; void DumpModule() {

//Получаем базовый адрес исполняемого приложения.

//Он может отличаться от такового у запущенного модуля,

//если этот код содержится в DLL.

HMODULE hModule = GetModuleHandle(NULL);

_tprintf(TEXT("with GetModuleHandle(NULL) = 0x%x\r\n"), hModule);

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

//Используем псевдопеременную __ImageBase для получения

//адреса текущего модуля hModule/hlnstance.

_tprintf(TEXT("with __ImageBase = 0х%х\r\n"), (HINSTANCE)&__ImageBase);

//Передаем GetModuleHandleEx адрес текущего метода

//DumpModule как параметр для получения адреса

//текущего модуля hModule/hInstance.

hModule = NULL; GetModuleHandleEx(

GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (PCTSTR)DumpModule,

&hModule);

_tprintf(TEXT("with GetModuleHandleEx = 0x%x\r\n"), hModule);

}

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

return(0);

}

Есть еще две важные вещи, касающиеся GetModuleHandle. Во-первых, она проверяет адресное пространство только того процесса, который ее вызвал. Если этот процесс не использует никаких функций, связанных со стандартными диалоговыми окнами, то, вызвав GetModuleHandle и передав ей аргумент «ComDlg32», вы получите NULL — пусть даже модуль ComDlg32.dll и загружен в адресное пространство какого-нибудь другого процесса. Во-вторых, вызов этой функции и передача ей NULL дает в результате базовый адрес ЕХЕ-файла в адресном пространстве процесса. Так что, вызывая функцию в виде GetModuleHandle (NULL) — даже из кода в DLL, — вы получаете базовый адрес ЕХЕ-, а не DLLфайла.

Описатель предыдущего экземпляра процесса

Я уже говорил, что стартовый код из библиотеки C/C++ всегда передает в функцию (w)WinMain параметр hPrevInstance как NULL. Этот параметр предусмотрен исключительно для совместимости с 16-разрядными версиями Windows и не имеет никакого смысла для Windows-приложений. Поэтому я всегда пишу заголовок

(w)WinMain так:

int WINAPI _tWinMain( HINSTANCE hInstanceExe, HINSTANCE,

PSTR pszCmdLine, int nCmdShow);

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

дение «parameter not referenced» («нет ссылки на параметр»). В Visual

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

Studio выбрано иное решение: сгенерированный мастером проекта-приложения на C++ использует макрос UNREFERENCED PARAMETER для удаления этих предупреждений следующим образом:

int APIENTRY _tWinMain(HINSTANCE hlnstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine,

int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine);

}

Командная строка процесса

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

Программа может анализировать и интерпретировать командную строку как угодно. Поскольку pszCmdLine относится к типу PSTR, а не PCSTR, не стесняйтесь и записывайте строку прямо в буфер, на который указывает этот параметр, но ни при каких условиях не переступайте границу буфера. Лично я всегда рассматриваю этот буфер как «только для чтения». Если в командную строку нужно внести изменения, я сначала копирую буфер, содержащий командную строку, в локальный буфер (в своей программе), который затем и модифицирую.

Указатель на полную командную строку процесса можно получить и вызовом функции GetCommandLine:

PTSTR GetCommandLine();

Она возвращает указатель на буфер, содержащий полную командную строку, включая полное имя (вместе с путем) исполняемого файла. Учтите, что GetCommandLine всегда возвращает адрес одного и того же буфера. Это еще одна причина, по которой следует воздерживаться от записи данных в pszCmdLine: при этом будет модифицирован исходный буфер, после чего восстановить исходную командную строку не удастся.

Во многих приложениях, безусловно, удобнее использовать командную строку, предварительно разбитую на отдельные компоненты, доступ к которым приложение может получить через глобальные переменные __argc и __argv (или __wargv), использовать которые, теперь не рекомендуется.

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

Функция CommandLineToArgoWиз ShellAPI расщепляет Unicode-строку на отдельные компоненты:

PWSTR* CommandLineToArgvW( PWSTR pszCmdLine,

int* pNumArgs);

Буква W в конце имени этой функции намекает на «широкие» (wide) символы и подсказывает, что функция существует только в Unicode-версии. Параметр pszCmdLine указывает на командную строку. Его обычно получают предварительным вызовом GetCommandLineW. Параметр pNumArgs — это адрес целочисленной переменной, в которой задается количество аргументов в командной строке. Функция CommandLineToArgvW возвращает адрес массива указателей на Un- icode-строки.

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

int nNumArgs;

PWSTR *ppArgv = CommandLineToArgvW(GetCommandLineW(), AnNumArgs);

//используйте эти аргументы… if (*ppArgv[1] == L'x') {

}

//освободите блок памяти

HeapFree(GetProcessHeap(), 0, ppArgv);

Переменные окружения

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

=::=::\ … VarName1=VarValue1\0 VarName2=VarValue2\0 VarName3=VarValue3\0 … VarNameX=VarValueX\0 \0

Первая часть каждой строки — имя переменной окружения. За ним следует знак равенства и значение, присваиваемое переменной. Обратите внимание, что не только первая строка (=:: = :: \), но и другие строки в этом блоке могут начинаться символом «=». Такие строки не считаются переменными окружения (подробнее об этом — ниже).

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

Показанные выше способы доступа к блоку переменных окружения дают разные результаты в зависимости от метода разбора. Первый способ позволяет получить блок переменных окружения целиком вызовом функции GetEnvironmentStrings (см. выше); извлечение отдельных переменных окружения иллюстрирует следующий пример:

void DumpEnvStrings() {

PTSTR pEnvBlock = GetEnvironmentStrings();

//разбор блока, имеющего следующий формат:

//=:: = ::\

//=

//var=value\0

//

//var=value\0\0

//Заметьте, что некоторые строки могут начинаться знаком '='.

//В этом примере приложение запущено с сетевого диска.

//[0] =::=::\

//[1] =C:=C:\Windows\System32

//[2] =ExitCode=00000000

//

TCHAR szName[MAX_PATH]; TCHAR szValue[MAX_PATH]; PTSTR pszCurrent = pEnvBlock; HRESULT hr = S_0K;

PCTSTR pszPos = NULL; int current = 0;

while (pszCurrent != NULL) {

//пропуск незначащих строк, таких как эта:

//"=::=::\"

if (*pszCurrent != TEXT('=')) { // поиск разделителя '='

pszPos = _tcschr(pszCurrent, ТЕХТ('='));

//указатель на первый символ значения pszPos++;

//копирование имени переменной…

size_t cbNameLength = // …без знака ' =' (size_t)pszPos - (size_t)pszCurrent - sizeof(TCHAR);

hr = StringCbCopyN(szName, MAX_PATH, pszCurrent, cbNameLength); if (FAILED(hr)) {

break;

}

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

//Копируем значение переменной с NULL-символом в конце

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

//предназначена только для отображения в UI.

hr = StringCchCopyN(szValue, MAX_PATH, pszPos, _tcslen(pszPos)+1); if (SUCCEEDED(hr)) {

_tprintf(TEXT("[Xu] %s=%s\r\n"), current, szName, szValue);

}else // что-то не так, проверить усечение if (hr == STRSAFE_E_INSUFFICIENT_BUFFER) {

_tprintf(TEXT("[%u] %s=%s…\r\n"), current, szName, szValue);

}else { // такого быть не должно

_tprintf(

TEXT("[%u] %s=???\r\n"), current, szName

)

break;

}

} else {

_tprintf(TEXT("[%u] %s\r\n"), current, pszCurrent);

}

//переходим к следующей переменной current++;

//Move to the end of the string. while (*pszCurrent != TEXT('\0'))

pszCurrent++;

pszCurrent++;

//проверим, не последняя ли это строка if (*pszCurrent == TEXT('\0'))

break;

};

// не забудьте освободить память! FreeEnvironmentStrings(pEnvBlock);

}

Недопустимые строки (строки, начинающиеся с «=», знака-разделителя) игнорируются, остальные строки поочередно подвергаются разбору. При этом знак «=» интерпретируется как разделитель имени переменной и ее значения. Закончив работу с блоком памяти, возвращенным GetEnvironmentStrings, освободите его вызовом FreeEnvironmentStrings:

BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock);

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

вании StringCchCopyN.

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

Второй способ доступа к переменным окружения поддерживается только для GUI-приложений и основан на параметре TCHAR* env[] входной функции. В отличие от параметра, возвращаемого GetEnvironmentStnngs, env -это массив указателей на строки, представляющие различные переменные окружения в обычном формате «имя=значение». За указателем на последнюю строку идет NULLуказатель, как показано ниже:

void DumpEnvVariables(PTSTR pEnvBlock[]) { int current = 0;

PTSTR* pElement = (PTSTR*)pEnvBlock; PTSTR pCurrent = NULL;

while (pElement != NULL) { pCurrent = (PTSTR)(*pElement); if (pCurrent == NULL) {

// переменные среды закончились pElement = NULL;

} else {

_tprintf(TEXT("[%u] %s\r\n"), current, pCurrent); current++;

pElement++;

}

}

}

Обратите внимание, что негодные строки, начинающиеся символом «=», удаляются до возврата env, поэтому вам не придется самостоятельно делать это.

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

XYZ= Windows (обратите внимание на пробел за знаком равенства)

ABC-Windows

и сравнив значения переменных XYZnABC, вы увидите, что система их различает, - она учитывает любой пробел, поставленный перед знаком равенства или после него. Вот что будет, если записать, скажем, так:

XYZ *Home (обратите внимание на пробел за знаком равенства)

XYZ*Work

Вы получите первую переменную с именем «XYZ», содержащую строку «Ноте», и вторую переменную «XYZ», содержащую строку «Work».

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

Примечание. При регистрации пользователя на входе в Windows система создает процесс-оболочку, связывая с ним группу строк — переменных окружения. Система получает начальные значения этих строк, анализируя два раздела в реестре. В первом:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\

Session Manager\Environment

содержится список переменных окружения, относящихся к системе, а во втором:

HKEY_CURRENT_USER\Environment

находится список переменных окружения, относящихся к пользователю, который в настоящее время зарегистрирован в системе. Пользователь может добавлять, удалять или изменять любые переменные через аплет System из Control Panel. В этом аплете надо открыть вкладку Advanced и щелкнуть кнопку Environment Variables — тогда на экране появится следующее диалоговое окно:

Модифицировать переменные из списка System Variables разрешается только пользователю с правами администратора. Кроме того, для модификации записей в реестре ваша программа может обращаться к Windows-функциям, позволяющим манипулировать реестром. Однако, чтобы изменения вступили в силу, пользователь должен выйти из системы и вновь войти в нее. Не-

которые приложения типа Explorer, Task Manager или Control Panel могут обновлять свои блоки переменных окружения на базе новых значений в реестре, когда их главные окна получают сообщение WM_SETTINGCHANGE. Например, если вы, изменив реестр, хотите, чтобы какие-то приложения соответственно обновили свои блоки переменных окружения, вызовите:

SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM) TEXTEnviron-

ment"));

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