![]() |
Главная Случайная страница Контакты | Мы поможем в написании вашей работы! | |
|
#include <windows.h>
#include <iostream.h>
void main(int argc, char *argv[])
{
cout<<"Открыто с помощью WinExec\n";
// Открытие целевого файла
if (WinExec("notepad readme.txt",SW_SHOW)<32)
MessageBox(NULL,"Не могу выполнить WinExec",NULL,MB_OK);
cout<<"Нажмите Enter\n";
MessageBox(NULL,"Нажмите OK для продолжения","Program Launched",MB_OK);
cout<<"Открыто с помощью ShellExecute\n";
if (ShellExecute(NULL,"open","readme.txt",NULL,NULL,SW_SHOW)<(HANDLE)32)
MessageBox(NULL,"Не могу выполнить ShellExecute\n",NULL,MB_OK);
}
Вызов CreateProcess
Вызовы наподобие ShellExecute и WinExec очень удобно использовать для выполнения простейших действий вроде открытия файлов и запуска программ. Если же вы хотите создать новый процесс, используя при этом некоторые дополнительные параметры, вам не обойтись без системного вызова CreateProcess. Описание аргументов, принимаемых этим вызовом, приведено в табл. 1 и 2.
Аргументы вызова CreateProcess Таблица 1
Аргумент | Описание |
lpApplicationName | Имя программы (или NULL, если имя программы указано в командной строке) |
lpCommandLine | Командная строка |
lpProcessAttributes | Атрибуты безопасности для дескриптора процесса, возвращаемого функцией |
lpThreadAttributes | Атрибуты безопасности для дескриптора потока, возвращаемого функцией |
bInheritHandlers | Указывает, наследует ли новыйЛ1роцесс дескрипторы, принадлежащие текущему процессу |
dwCreationFlags | Параметры создания процесса (см. табл. 2) |
lpEnvironment | Значения переменных окружения (или NULL, в случае если наследуется текущее окружение) |
lpCurrentDirectory | Текущий каталог (или NULL, если используется текущий каталог текущего процесса) |
lpStartupInfo | Структура STARTUPINFO, содержащая информацию о запуске процесса |
lpProcessInformation | Возвращаемые функцией дескрипторы и идентификаторы ID процесса и потока |
Флаг | Значение |
CREATE_DEFAULT_ERROR_MODE | He наследовать текущий режим сообщений об ошибках (см. SetErrorMode) |
CREATE_NEW_CONSOLE | Создать новую консоль |
CREATE_NEW_PROCESS_GROUP | Создать новую группу процессов |
CREATE_SEPARATE_WOW_VDM | Запустить 16-битное приложение в его собственном адресном пространстве |
CREATE_SHARED_WOW_VDM | Запустить 16-битное приложение в адресном пространстве общего доступа |
CREATE_SUSPENDED | Создать процесс в приостановленном состоянии (см, ResumeThread) |
CREATE_UNICODE_ENVIRONMENT | Блок переменных окружения записан в формате UNICODE |
DEBUG_PROCESS | Запустить процесс в отладочном режиме |
DEBUG_ONLY_THIS_PROCESS | Предотвратить отладку процесса текущим отладчиком (используется при отладке родительского процесса) |
DETACHED_PROCESS | Новый консольный процесс не имеет доступа к консоли родительского консольного процесса |
Аргумент lpStartupInfo - это указатель на структуру STARTUPINFO, Поля этой структуры содержат заголовок консоли, начальный размер и позицию нового окна и перенаправление стандартных потоков ввода/вывода. Новая программа может проигнорировать все эти параметры в зависимости от собственного желания. Поле dwFlags этой структуры содержит флаги, установленные в соответствии с тем, какие из остальных полей структуры вы хотели бы использовать при запуске новой программы. Например, если вы сбросите флаг STARTF_USEPOSITION, поля dwX и dwY структуры STARTUPINFO, содержащие координаты основного окна запускаемой программы, будут проигнорированы. Часто нет надобности использовать какие-либо из этих полей. В этом случае вы все равно обязаны передать функции CreateProcess корректный указатель на пустую структуру STARTUPINFO. He забудьте указать размер структуры в поле cd и присвоить полю dwFlags значение 0.
Функция CreateProcess записывает в аргумент lpProcessInfomation указатель на структуру, содержащую дескрипторы и идентификаторы ID нового процесса и потока. Доступ к этим дескрипторам определяется аргументами lpProcessAttributes и lpThreadAttributes.
Обратите внимание, что некоторые аргументы функции CreateProcess относятся к консольным приложениям. Другие имеют значение для всех типов программ. Зачастую при обращении к CreateProcess можно не заполнять структуру STARTUPINFO, однако в любом случае вы обязаны передать функции указатель на существующую в памяти структуру, даже если эта структура не заполнена. Функция Create Process возвращает значение типа BOOL, но по завершении работы чрезвычайно полезная для программиста информация размещается функцией в структуре типа PROCESS_INFORMATION, указатель на которую возвращается при помощи параметра lpProcessInformafion этой функции. В структуре PROCESS_INFORMATION содержится идентификатор и дескриптор нового процесса, а также идентификатор и дескриптор самого первого потока, принадлежащего новому процессу. Эти сведения могут использоваться для того, чтобы сообщить о новом процессе другим программам, а также для того, чтобы контролировать новый процесс,
При создании процесса с использованием вызова CreateProcess можно передать новому процессу по наследству некоторые вещи, в частности дескрипторы открытых файлов. Однако, к сожалению, это редко когда бывает полезным, за исключением случаев, когда вы хотите объединить стандартные потоки ввода/вывода нескольких консольных приложений. В этой ситуации дескрипторы файлов стандартных потоков ввода/вывода обладают заранее определенными значениями. Даже если новый процесс получает по наследству дескриптор открытого файла, он не может определить его значения, если только оно не определено заранее. Конечно, вы можете передать дескриптор в командной строке или в переменной окружения, однако это не очень удобный вариант.
Обладай дескриптором процесса, вы можете управлять процессом при помощи вызовов, перечисленных в табл. 3, Чтобы определить момент завершения процесса, можно воспользоваться одним из нескольких методов. Во-первых, можно использовать вызов GetExitCodeProcess. Этот вызов возвращает либо значение STILL_ACTIVE (если процесс все еще продолжает работу), либо код завершения процесса (если процесс завершен), В качестве одногоиз аргументов вы передаете этой функции указатель на переменную, в которую помещается возвращаемое значение. Узнать дескриптор текущего процесса можно при помощи функции GetCurrentProcess, Чтобы управлять процессом из другого процесса, вы должны обратиться к функции OpenProcess, которой необходимо передать идентификатор ID-процесса. Чтобы открыть процесс для желаемого доступа, вы должны обладать необходимыми разрешениями на доступ к процессу. Процесс, создающий новый процесс при помощи функции CreateProcess, уже обладает дескриптором нового процесса (этот дескриптор помещается в структуру PROCESS_INFORMATION).
Таблица 3. Системные вызовы управления процессом
Вызов | Назначение | Требуемый уровень доступа |
CreateRemoteThread | Создает поток в другом процессе | PROCESS_CREATE_THREAD |
GetExltCodeProcess | Возвращает код завершения процесса | PROCESS_QUERY_INFORMATION |
GetGuiRecources | Определяет, сколько объектов USER или GDI (Graphical Device Interface) используется процессом | PROCESS_QUERY_INFORMATION |
SetPriorityClass | Устанавливает базовый приоритет процесса | PROCESS_SET_INFORMATION |
GetPriorityClass | Возвращает базовый приоритет процесса | PROCESS_QUERY_INFORMATION |
SetProcessAfflnltyMask | Определяет, какие из процессоров используются процессом в качестве основных | PROCESS_SET_INFORMATION |
GetProcessAffinityMask | Устанавливает, какие из процессоров используются процессом в качестве основных | PROCESS_QUERY_INFORMATION |
SetProcessPriorityBoost | Позволяет или запрещает Windows динамически изменять приоритет процесса | PROCESS_SET_INFORMATION |
GetProcessPriorityBoost | Возвращает статус изменения приоритета процесса | PROCESS_GET_INFORMATION |
SetProcessShutDownParameters | Определяет, в каком порядке система закрывает процессы при завершении работы всей системы | N/A (необходимо вызвать изнутри процесса) |
GetProcessShutDownParameters | Возвращает статус механизма завершения работы системы | PROCESS_QUERY_INFORMATION N/A (необходимо вызвать изнутри процесса) |
SetProcessWorkingSetSize | Устанавливает минмальный и максимальный допустимый объем физической оперативной памяти, используемый процессом | PROCESS_SET_INFORMATION |
GetProcessWorkingSetSize | Возвращает информацию об использовании физической памяти процессом | PROCESS_GET_INFORMATION |
TerminateProcess | Корректное завершение работы процесса | PROCESS_TERMINATE |
ExitProcess | Немедленное завершение процесса | N/A (необходимо вызвать изнутри процесса) |
GetProcessVersion | Возвращает версию Windows, в среде которой хотел бы работать процесс | N/A (использует ID процесса) |
GetProcessTimes | Возвращает степень использования CPU процессом | PROCESS_GET_INFORMATION |
GetStartUpInfo | Возвращает структуру STARTUPINFO, переданную процессу при обращении к CreateProcess | N/A (необходимо вызвать изнутри процесса) |
Еще один способ определения текущего состояния процесса подразумевает использование функции WaitForSingleObject.. Основное назначение WaitForSingleObject — определить, находится ли некоторый дескриптор в сигнальном состоянии. Дескриптор процесса переходит в сигнальное состояние тогда, когда процесс завершает свою работу. При обращении к функции WaitForSingleObject вы указываете дескриптор процесса и интервал времени в миллисекундах. Если интервал времени равен 0, функция завершает работу немедленно, возвращая текущее состояние процесса. Если интервал времени равен константе INFINIT, функция будет ждать до тех пор, пока интересующий вас процесс не завершит работу. Если вы укажете конкретное цифровое значение интервала времени, функция будет ожидать завершения процесса в течение указанного времени, а затем вернет управление вызвавшей ее программе. Если в течение указанного времени интересующий вас процесс завершит работу, функция WaitForSingleObject вернет управление вызвавшей программе и сообщит ей, что дескриптор целевого процесса перешел в сигнальное состояние. В противном случае эта функция вернет негативный ответ.
Вне зависимости от того, в каком состоянии находится дескриптор целевого процесса, функция WaitForSingleObject также возвращает значение, отражающее ее успешное выполнение. Имейте в виду: то обстоятельство, что дескриптор так и не перешел в сигнальное состояние, не является ошибкой. Чтобы определить состояние процесса, необходимо сравнить значение, которое вернула функция, со значениями WAIT_OBJECT_0 (сигнальное состояние) и WAIT_TIMEOUT (процесс продолжает функционировать). В случае ошибки функция вернет значение WAIT_FAILED. Есть еще одно значение, которое может вернуть эта функция. Это значение — WAIT_ABANDONED, однако, работая с процессами, вы никогда не столкнетесь с этим
значением. Чтобы иметь возможность ожидать завершения процесса, вы должны обладать открытым дескриптором этого процесса с привилегией SYNCHRONIZE.
Помните, что идентификатор ID-процесса — это не то же самое, что дескриптор процесса. Дескрипторы процессов нельзя просто так передавать из процесса в процесс. Это означает, что если вы хотите управлять процессом из другого процесса, вы обязаны прежде всего каким-либо образом получить дескриптор управляемого процесса. Для идентификации процесса другими процессами служит идентификатор ID этого процесса. Этот идентификатор можно передать из процесса в процесс. Преобразовать идентификатор ID-процесса в его дескриптор можно при помощи функции OpenProcess, однако для этого вы должны обладать необходимыми привилегиями. Узнать идентификатор текущего процесса можно при помощи функции GetCurrentProcessId. Используя этот вызов, вы можете узнать идентификатор собственного процесса и передать этот идентификатор другому процессу. Получив ID вашего процесса, другой процесс сможет открыть его дескриптор.
При обращении к функции OpenProcess необходимо указать требуемый уровень доступа к открываемому процессу. Иногда получить доступ к процессу на любом из возможных уровней нельзя, поэтому, выбирая уровень, старайтесь использовать именно тот, который нужен вам для выполнения вашей задачи, и не более того. Например, чтобы узнать код завершения процесса, достаточно владеть уровнем доступа PROCESS_QUERY_INFORMATION. Чтобы иметь возможность завершить работу процесса, необходимо обладать уровнем доступа PROCESS_TERMINATE. Уровни доступа к процессу перечислены в табл. 3. Вы можете запросить предоставление вам полного набора прав доступа к процессу. Для этого предназначен уровень доступа PROCESS_ALL_ACCESS.
При помощи вызова LoginUser программа, обладающая необходимой привилегией (конкретно SE_TCB_NAME), может определить лексему (token) подключенного к системе пользователя. Обладая этой лексемой, можно запустить какой-либо процесс от имени пользователя системы. Другими словами, действия, которые выполняет процесс, будут рассматриваться системой как действия, выполняемые пользователем системы. Запуск процесса от имени пользователя осуществляется при помощи вызова CreateProcessAsUser (только в Windows 2000). При этом программа будет запущена от лица пользователя, обладающего указанной лексемой.
В листингах 2 и 3 приведены два простых консольных приложения. Первая программа (MASTER) запускает вторую (SLAVE) и переходит в режим ожидания. Программа SLAVE читает идентификатор процесса (PID, Process Identifier) запустившей ее программы из командной строки и ожидает завершения работы программы MASTER. В командной строке программы MASTER вы можете указать полный путь к исполняемому файлу программы SLAVE.
Обе программы иллюстрируют несколько важных технологий:
· использование CreateProcess;
· использование OpenProcess;
· использование WaitForSingleObject.
Обратите внимание, что MASTER не использует большую часть аргументов функции CreateProcess, поэтому в данном конкретном случае для запуска SLAVE вполне можно использовать вызов WinExec (см. комментарии в листинге 2).
Листинг 2 Master
#include <windows.h>
#include <iostream.h>
#include <stdio.h>
#include <string.h>
void main(int argc, char *argv[])
{
char cmd[128];
if (argc!=1)
strcpy(cmd,argv[1]);
else
strcpy(cmd,"slave.exe");
int pid=GetCurrentProcessId();
sprintf(cmd+strlen(cmd)," %d",pid);
cout<<"Master: Starting:" << cmd << "\n";
cout.flush();
// Здесь пример использования WinExec
// if (WinExec(cmd,SW_SHOW)<32)
// Если требуется использовать возможности CreateProcess:
STARTUPINFO info;
memset(&info,0,sizeof(info));
info.cb=sizeof(info);
PROCESS_INFORMATION pinfo;
if (!CreateProcess(NULL,cmd,NULL,NULL,FALSE,NORMAL_PRIORITY_CLASS,
NULL,NULL,&info,&pinfo))
{
cout<<"Master: Slave процесс не запущен\n";
cout<<"Master: попытайтесь указать имя процесса в ком. строке\n";
}
cout<<"Master: Sleeping\n";
cout.flush();
Sleep(15000);
cout<<"Master: Exiting\n";
exit(0);
}
Листинг 3 Slave
#include <windows.h>
#include <iostream.h>
#include <stdio.h> // temp
void main(int argc,char *argv[])
{
if (argc!=2)
{
cerr<<"Slave: Запустите MASTER.EXE\n";
exit(1);
}
int pid=atoi(argv[1]);
HANDLE process=OpenProcess(PROCESS_QUERY_INFORMATION|SYNCHRONIZE,FALSE,pid);
if (!process) cout<<"Slave: Ошибка при открытии процесса\n";
cout<<"Slave: Подождите завершения работы МАСТЕРА\n";
cout.flush();
if (WaitForSingleObject(process,INFINITE)==STATUS_WAIT_0)
cout<<"Slave: МАСТЕР завершил работу\n";
else
cout<<"Slave: Неизвестная ошибка\n";
exit(0);
}
Задания и рабочие наборы
Обычно каждому процессу в Windows 2000 назначается так называемый рабочий набор (working set), Рабочий набор процесса определяет, какой объем физической памяти диспетчер памяти Windows пытается сохранить за данным процессом, В рабочем наборе указывается минимальное и максимальное количество страниц памяти, которые должны принадлежать данному процессу. Узнать текущий размер можно при помощи вызова GetProcessWorkingSize. Если объем памяти, доступный для других приложений, становится неприемлемо маленьким, система может нарушить границы, установленные в рабочем наборе той или иной программы.
Обладая необходимыми для этого привилегиями, программа может изменить собственный рабочий набор при помощи функции SetProcessWorkingSetSize. Если обе границы становятся равными 0xFFFFFFFF, Windows сбрасывает на диск всю память, принадлежащую процессу.
Еще одним методом управления процессами в Windows является задание (job). Задание — это группа связанных между собой процессов. Создать задание можно при помощи функции CreateJobObject, а открыть существующее задание можно при помощи функции OpenJobObject. Обладая дескриптором задания, вы можете добавить к нему любые другие процессы при помощи функции Assign ProcessToJobObject,
Зачем может потребоваться объединять процессы в группу? Этому может быть несколько причин. Например, используя функцию TerminateJobObject, вы можете разом завершить работу всех процессов одного задания. При помощи вызова SetInforinationJobObject вы можете установить рабочий набор одновременно для всей группы процессов, входящих в задание. Этот же вызов можно использовать для назначения ограничений всем процессам, входящим в задание. В частности, вы можете запретить всему заданию обращаться к системному вызову ExitWindows, читать содержимое системного буфера обмена или использовать какие-либо дескрипторы (например, дескрипторы окон) других процессов. Если вы запретили процессам одного задания доступ к пользовательским дескрипторам, процесс, не принадлежащий заданию, может предоставить процессам задания доступ к пользовательскому дескриптору при помощи функции UserHandleGrantAccess,
Помимо прочего операционная система позволяет получать статистику, связанную с процессами задания. Для этого служит вызов QueryInformationObject. Используя этот вызов, вы можете получить информацию о том, какую нагрузку на центральный процессор создают процессы, входящие в состав задания, а также другую подобную информацию. Кроме того, при помощи этого вызова можно получить информацию о параметрах конфигурации задания, значения которым присваиваются при помощи функции SetInformationJobObject.
Потоки
При создании процесса в системе появляется новый программный поток, принадлежащий этому процессу. Вначале любой только что созданный процесс обладает лишь одним потоком. Этот поток может создавать новые потоки, а эти новые потоки, в свою очередь, могут создавать другие новые потоки. Процесс продолжает свое существование до тех пор, пока в его владении находится по крайней мере один программный поток (или до тех пор, пока не произойдет что-либо, в результате чего весь процесс или все задание не прекратит работу, например, обращение к функции TerminateProcess).
Зачем процессу несколько потоков? Потоки могут выполнять какие-либо действия в фоновом режиме относительно вашей основной программы. Например, вы можете создать новый программный поток, который будет в фоновом режиме осуществлять вывод информации на принтер. Потоки удобно использовать также в случае, если блокирование или подвисание какой-либо процедуры не должно стать причиной нарушений функционирования основной программы. Например, в то время как основная программа выполняет сложные математические вычисления, отдельный программный поток может осуществлять обмен данными через асинхронный последовательный канал связи (например, через модем). В случае замедления передачи данных через канал или в случае подвисания модема функционирование основной программы не будет нарушено.
Базовый системный вызов, предназначенный для создания потока, — это CreateThread. Однако на практике программисты фактически никогда его не используют. Почему? Потому что этот вызов создает абсолютно пустой поток, который в изначальном виде не может использоваться вашей библиотекой (С или MFC).
Если для разработки программы вы используете C++ или MFC, вы должны воспользоваться для создания потока специальной функцией, включенной в состав применяемой вами библиотеки. Эта функция не только создает новый программный поток, но и размещает в нем специфический код, который необходим для начала работы потока. Благодаря этому коду библиотека сможет работать с только что созданным программным потоком. Если для создания потока вы обратитесь к функции CreateThread, скорее всего, ваша библиотека сработает некорректно.
В состав большинства библиотек обычно входит специальная функция, предназначенная для создания нового потока. Например, в составе стандартной библиотеки C++ присутствуют функции _beginthread и _beginthreadex (функция _beginthreadex принимает несколько дополнительных аргументов и позволяет создавать потоки, находящиеся в приостановленном состоянии, а также она возвращает вам дескриптор нового потока). Для завершения работы потока также используются специальные функции. Эти функции обеспечивают корректное завершение работы потока средствами используемой вами библиотеки. В библиотеке C++ для этой цели предназначены функции _endthread и _endthreadex (в зависимости от того, какая из функций использовалась для того, чтобы создать поток).
Если в своей программе вы намерены использовать вызов _beginthread или любую другую подобную функцию, вы должны изменить конфигурацию вашего проекта таким образом, чтобы при компиляции использовалась многопоточная версия библиотеки. Этот параметр настраивается при помощи вкладки C/C++ в разделе Code Generation settings (Настройки генерации кода). Параметр имеет название Use Run-Time Library (Используемая библиотека времени исполнения).
Помните: Windows распределяет процессорное время не между процессами, а между потоками. Каждый поток в системе работает независимо. Количество процессорного времени, выделяемое конкретному потоку, определяется многими факторами. Во-первых, каждый процесс обладает собственным базовым уровнем приоритета, который присваивается всем принадлежащим ему потокам. Во-вторых, каждый из потоков обладает собственным приоритетом, который добавляется к базовому значению. Наконец, сама операционная система имеет право динамически изменить приоритет некоторого потока, если посчитает, что так будет лучше. Например, если система обнаружит, что устройство ввода готово передать одному из потоков какие-либо данные, и ожидает, пока управление будет передано этому потоку, система может временно повысить приоритет этого потока.
Для управления потоками служат системные вызовы, перечисленные в табл. 4.
Системные вызовы для управления потоками Таблица 4.
Функция | Требуемый уровень доступа | Описание |
AttachThreadInput | Нет | Связывает обработку ввода потока с другим потоком. Позволяет передавать фокус ввода окнам другого потока, а также использовать общее состояние ввода |
CreateRemoteThread | PROCESS_CREATE_THREAD | Создает поток в другом процессе |
CreateThread | Нет | Создает поток в текущем процессе |
ExitThread | Нет | Завершает работу текущего потока |
TerminateThread | THREAD_TERMINATE | Останавливает работу потока без выполнения необходимых подготовительных процедур |
GetCurrentThread | Нет | Возвращает дескриптор текущего потока |
GetCurrentThreadId | Нет | Возвращает идентификатор ID текущего потока |
GetExitCodeThread | THREAD_QUERY_INFORMATION | Возвращает код завершения потока |
SetThreadPriority | THREAD_SET_INFORMATION | Устанавливает уровень приоритета потока |
GetThreadPriority | THREAD_QUERY_INFORMATION | Возвращает уровень приоритета потока |
SetThreadPriority Boost | THREAD_SET_INFORMATION | Разрешает или запрещает динамическое изменение уровня приоритета данного потока |
GetThreadPriorityBoost | THREAD_QUERY_INFORMATION | Возвращает статус динамического изменения приоритета для данного процесса |
GetThreadTimes | THREAD_QUERY_INFORMATION | Возвращает время, когда был создан или уничтожен поток и какое количество процессорного времени было им использовано |
SuspendThread | THREAD_SUSPEND_RESUME | Временно приостанавливает выполнение потока |
ResumeThread | THREAD_SUSPEND_RESUME | Продолжает работу потока, приостановленного в результате обращения к SuspendThread |
SetThreadAfflnltyMask | THREAD_SET_INFORMATION | Устанавливает, какие процессоры могут использоваться для выполнения данного потока |
SetThreadIdealProcessor | THREAD_SET_INFORMATION | Устанавливает, какой процессор предпочтительней использовать для выполнения потока |
SwItchToThread | Нет | Переключает процессор на выполнение другого потока (неизвестно, какого именно) |
Пример
Очень часто компьютеры в художественных фильмах обладают совершенно неудобными, неестественными, а иногда и отталкивающими пользовательскими интерфейсами. Обычно если происходит что-то критическое, компьютер сопровождает это событие леденящим душу звуковым сигналом, и все зрители понимают: катастрофа совсем рядом.
Конечно, нам с вами не удастся воспроизвести что-либо подобное, однако попробовать стоит. Обратите внимание на листинг 4. Эта простая программа выводит на экран простое диалоговое окно с текстовым сообщением и при этом каждую секунду издает короткий звуковой сигнал, Для воспроизведения этого сигнала используется отдельный программный поток.
Листинг 4. Тревога!
#include <windows.h>
#include <process.h> // необходимо для обращения к _beginthread
HANDLE mainthread;
void beepthread(void *)
{
DWORD xitcode;
while (GetExitCodeThread(mainthread,&xitcode)&&xitcode==STILL_ACTIVE)
{
MessageBeep(-1);
Sleep(1000);
}
}
void main()
{
mainthread=GetCurrentThread();
_beginthread(beepthread,0,NULL);
MessageBox(NULL,"Red Alert","Alert",MB_OK);
}
Основная программа сохраняет дескриптор потока в глобальной переменной. Это позволяет потоку, воспроизводящему звук, определить момент, когда основная программа завершит свою работу. В этот момент поток, воспроизводящий звук, также завершит свою работу.
Конечно, для того чтобы установить факт завершения работы основного потока, можно использовать вызов WaitForSingleObject, однако в нашем случае удобнее обратиться к GetExitCodeThread. Если эта функция возвращает значение STILL_ACTIVE, значит, основной поток продолжает функционирование, а следовательно, поток beepthread может продолжать пищать.
Что произойдет, если дочерний поток не будет следить за завершением работы родительского потока? В нашем случае — ничего. Программа будет вести себя абсолютно также. Попробуйте заменить оператор while в листинге 4 на оператор while(1). Все работает так же, как и до модификации исходного кода. Дело в м, что при завершении работы функции main библиотека времени исполнения С обращается к exit, a exit вызывает функцию ExitProcess, которая автоматически уничтожает все потоки процесса.
Если говорить о библиотеке времени исполнения С, то можно отметить, что создаваемый поток не обращается к функциям этой библиотеки, поэтому для его создания вместо специальной библиотечной функции _ beginthread можно использовать базовый системный вызов CreateProcess.
Локальная память потоков
Иногда бывает удобно использовать некоторую область памяти, которая относится к конкретному потоку, но не является локальной переменной. Представьте, что вы разрабатываете сетевой сервер. Каждый из клиентов сервера обслуживается отдельным потоком. Чтобы выполнить некоторое действие от имени клиента, потоки обращаются к некоторой функции (допустим, ее имя — DoWork). Существуют причины, по которым вы хотите ограничить количество обращений каждого из клиентов к этой функции. Допустим, каждый из клиентов (а следовательно, каждый из потоков) имеет право обратиться к этой функции не более 10 раз.
Каким образом вы можете организовать подобный счетчик обращений? Глобальная переменная не подходит. Если вы используете глобальную переменную, ее значение будет увеличиваться каждый раз при обращении любого потока к функции DoWork. Таким образом, если десять потоков обратятся к этой функции по одному разу, значение счетчика станет равным 10, а это не то, что нам нужно. Локальная переменная тоже не подходит, так как при каждом очередном обращении к функции DoWork локальной переменной будет присваиваться нулевое значение.
Конечно, проблему можно решить несколькими способами. Например, каждый поток может выделить специальную область памяти, принадлежащую лично ему и каждый раз при обращении к DoWork передавать этой функции указатель на эту область в качестве аргумента. Возможно, этот способ подойдет для решения конкретно данной проблемы, однако, если вам потребуется несколько подобных переменных, вы можете столкнуться с новыми проблемами.
Рассмотрим решение, предлагаемое операционной системой. Предположим, что вы создаете таблицу с двумя колонками. Таким образом, каждая запись таблицы имеет два поля: в первом поле содержится идентификатор ID-потока, а во втором — 32-битное число. Каждый раз при обращении потока к функции DoWork функция определяет ID текущего потока (при помощи вызова GetCurrentThreadID) и ищет его в таблице. Если запись с таким идентификатором есть, функция ис
пользует эту запись. Если такой записи нет, в таблицу вносится новая запись, содержащая ID текущего потока. В любом случае в вашем распоряжении оказывается табличная запись, принадлежащая текущему потоку. Во втором поле этой записи можно хранить счетчик обращения к функции DoWork для потока с заданным ID. Если поток завершает работу, запись с соответствующим ID удаляется из таблицы, и на ее месте можно расположить запись, принадлежащую другому потоку.
Именно по такому принципу работает локальная память потоков (Thread Local Storage, TLS). Для каждого из процессов создается набор внутренних таблиц. Windows может создать до 64 таких таблиц для каждого процесса. Таким образом, вы можете использовать 64 различные переменные TLS. Когда вы обращаетесь к TlsAlloc, Windows выделяет вам одну таблицу TLS. После этого вы можете использовать вызовы TlsSetValue и TlsGetValue для того, чтобы установить или прочитать значение из таблицы. При этом операционная система обращается именно к той записи в таблице, которая соответствует текущему потоку. Если таблица вам больше не нужна, вы можете обратиться к функции TlsFree для того, чтобы освободить ее. В каждой записи таблицы можно хранить любое 32-битное число, однако чаще всего таблица TLS используется для хранения указателей на класс или структуру, содержащую все необходимые для потока переменные.
Конечно, TLS — не единственный метод решения проблемы. В частности, вы можете разработать собственное решение, которое будет работать подобно TLS. Другой вариант предусматривает передачу функции в качестве одного из аргументов указателя на область памяти, принадлежащую потоку.
Листинг 5 демонстрирует применение обоих методов. Основная программа создает два потока (t_l и t_2). Каждый из потоков случайным образом выбирает два числа (п1 и п2) и обращается к двум разным функциям (fl и f2) количество раз, соответствующее этим числам. Функция fl хранит количество обращений в таблице TLS, которую создает функция main. Чтобы извлечь количество обращений к функции fl из таблицы TLS, в программе используется функция getflcount.
Функция f2 также следит за количеством обращений, однако эта функция хранит счетчик в локальной переменной, которая передается ей как аргумент. Этот метод приемлем в ситуации, когда основная программа имеет возможность создать локальную переменную.
Листинг 5. Использование TLS
#include <windows.h>
#include <iostream.h>
#include <process.h>
#include <stdlib.h>
#include <time.h>
volatile int t1done=0;
volatile int t2done=0;
DWORD index;
void display(char *display, int f1, int f2)
{
cout<<display<<" f1="<<f1<<" f2="<<f2<<"\n";
}
int getf1count()
{
return (int)TlsGetValue(index);
}
void f1()
{
int ct=(int)TlsGetValue(index);
TlsSetValue(index,(void *)++ct);
// здесь можете вставить полезный код
}
void f2(int &ctr)
{
ctr++;
// здесь можете вставить полезный код
}
// main thread
void t_1(void *)
{
int callf1=0;
int callf2=0;
int n1,n2;
srand((unsigned)time(NULL) +100);
n1=rand()%9+1; // количество вызовов f1
n2=rand()%9+1; // количество вызовов f2
display("T1: Вызывов",n1,n2);
while (n1--) f1();
callf1=getf1count();
while (n2--) f2(callf2);
display("T1: Результат",callf1,callf2);
t1done=1;
}
void t_2(void *)
{
int callf1=0;
int callf2=0;
int n1,n2;
srand((unsigned)time(NULL)+245);
n1=rand()%9+1; // количество вызовов f1
n2=rand()%9+1; // количество вызовов f2
display("T2: Вызывов",n1,n2);
while (n1--) f1();
callf1=getf1count();
while (n2--) f2(callf2);
display("T2: Результат",callf1,callf2);
t2done=1;
}
void main()
{
index=TlsAlloc();
_beginthread(t_1,0,NULL);
_beginthread(t_2,0,NULL);
while (t1done==0||t2done==0);
TlsFree(index);
cout.flush();
MessageBox(NULL,"Done","Debug",MB_OK);
}
Нити
Переключение между потоками осуществляется в соответствии с принципом приоритетности. Если система решает, что ваше время истекло, она «отбирает» центральный процессор у вашего потока и передает его в распоряжение другого потока. Однако в некоторых ситуациях может потребоваться более изощренный контроль над передачей управления между участками кода программы. Именно в этих случаях применяют нити (fibers).
Преобразовать поток в нить можно при помощи функции ConvertThreadToFiber. Каждый поток может обладать несколькими нитями. Когда система переключается с одного потока на выполнение другого, начинается выполнение текущей нити для данного потока. Если в потоке только одна нить, то вы, очевидно, не замените разницы — код, который раньше был частью потока, стал частью нити. Однако если в потоке несколько нитей, то вы можете управлять переключением между ними при помощи функции SwitchToFiber. Создать новую нить в текущем потоке можно при помощи функции CreateFiber.
Зачем нужны нити? Нити напоминают несколько усложненную форму длинного перехода (подобно функции long jump библиотеки С). В Windows 2000 нити используются нечасто, однако в некоторых других операционных системах подобный механизм находит широкое применение. Таким образом, нити чрезвычайно удобны в случае, если требуется адаптировать для Windows код, написанный для какой-либо другой операционной системы (например, Unix), в которой при помощи подобного механизма имитируется выполнение нескольких потоков в рамках одного процесса.
Для операционной системы каждая нить — это поток, который выполняет эту нить в текущий момент. Единственное отличие состоит в том, что нить может контролировать, какой из других нитей того же самого потока будет передано управление. Обратите внимание на то, что переключение между потоками осуществляется в прежнем режиме. Нити контролируют взаимную передачу управления только в рамках потока, к которому они принадлежат. Другие потоки выполняются независимо от этого.
Простой пример использования нитей приводится в листинге 6. Поток main преобразует сам себя в нить, а затем создает еще три нити. После этого главная нить потока по очереди передает управление каждой из трех остальных нитей. Выполнив действие, каждая из трех нитей возвращает управление главной нити.
Если вам не требуется осуществлять контроль над переключением между выполнением разных участков кода, вы вполне сможете обойтись без использования нитей. Обычно большинство задач, связанных с параллельным выполнением различных действий, можно решить при помощи потоков.
Листинг 6. Использование нитей
#include <windows.h>
#undef MessageBox
#define MessageBox(s) MessageBoxA(NULL,s,"Fiber Demo",MB_OK)
void *fiber[4];
void fiber0(void *) // основная нить
{
while (1)
{
for (int i=1;i<4;i++)
SwitchToFiber(fiber[i]);
}
}
void CALLBACK fiber1(void *)
{
while (1)
{
MessageBox("Fiber 1");
SwitchToFiber(fiber[0]);
}
}
void CALLBACK fiber2(void *)
{
while (1)
{
MessageBox("Fiber 2");
SwitchToFiber(fiber[0]);
}
}
void CALLBACK fiber3(void *)
{
while (1)
{
MessageBox("Fiber 3");
SwitchToFiber(fiber[0]);
}
}
void main()
{
fiber[0]=ConvertThreadToFiber(NULL);
fiber[1]=CreateFiber(0,fiber1,NULL);
fiber[2]=CreateFiber(0,fiber2,NULL);
fiber[3]=CreateFiber(0,fiber3,NULL);
fiber0(NULL);
}
АРС
Модуль Windows Executive поддерживает несколько необычную технологию под названием Asynchronous Procedure Call (АРС). Эта технология позволяет одному потоку асинхронно инициировать выполнение некоторой функции в другом потоке. Иными словами, по команде первого потока выполнение второго потока асинхронно прерывается, второй поток выполняет некоторую функцию, после чего выполнение второго потока продолжается. Звучит впечатляюще, однако, к сожалению, Win32 накладывает на потоки АСР некоторые немаловажные ограничения.
Система не может просто так взять и прервать выполнение последовательности команд потока, так как это может привести к возникновению проблем синхронизации и т. п. Чтобы не возникло подобных проблем, система разрешает потоку выполнить назначенную для него функцию АРС только в случае, если поток находится в так называемом «настороженном» состоянии (alertable state). Перевести поток в «настороженное» состояние можно при помощи вызовов SleepEx, SIngalObjectAndWait, WaitForS-ingleObjectEx, WaitForMultipleObjectsEx и MsgWaitForMultipleObjectsEx.
На самом деле вызов АРС, предназначенный для выполнения в некотором потоке, ставится в очередь, соответствующую этому потоку. Как только поток переходит в «настороженное» состояние, немедленно происходит последовательное выполнение всех функций АРС, находящихся в очереди, соответствующей этому потоку. Если функция АРС поставлена в очередь потока до того, как сам поток начал работу, эта функция будет выполнена в самом начале выполнения потока.
Технология АРС широко используется многими встроенными механизмами Windows (например, для организации перекрывающегося (overlapped) ввода/вывода. Чтобы поставить функцию АРС в очередь некоторого потока, следует использовать системный вызов QueueUserAPC. В качестве аргументов эта функция принимает указатель на функцию АРС, дескриптор потока и данные, которые необходимо передать функции АРС. При обращении к QueueUserAPC указанная функция АРС ставится в очередь для выполнения в указанном потоке. Как только поток переходит в «настороженное» состояние, он одну за другой выполняет все функции АРС, поставленные в его очередь.
Исходный код простой программы, использующей QueueUserAPC, приведен в листинге 7. Функция main программы создает новый поток и на короткое время переходит в режим ожидания для того, чтобы поток успел начать работу. После этого программа обращается к функции QueueUserAPC. Теперь, как только созданный нами поток (tl) перейдет в «настороженное» состояние, он выполнит назначенную для него функцию АРС (apcfunc). В нашем примере поток переходит в «настороженное» состояние при помощи вызова SleepEx (но не Sleep). Однако, если основная программа не будет ждать, пока новый поток начнет работу, а сразу же, создав поток, поставит в его очередь функцию АРС, эта функция может быть выполнена в момент, когда поток создан, но не успел начать работу. Чтобы проверить это, закомментируйте вызов Sleep в функции main.
Листинг 7. Использование АРС
#include <windows.h>
#include <iostream.h>
volatile int waiting=0;
void CALLBACK apcfunc(DWORD)
{
waiting=1;
}
DWORD CALLBACK t1(LPVOID)
{
cout<<"Поток начал работу\n";
cout.flush();
Sleep(15000);
cout<<"Переходим в настороженное состояние\n";
cout.flush();
SleepEx(10000,TRUE);
MessageBox(NULL,"T1 завершил работу","Debug",MB_OK);
return 0;
}
void main()
{
HANDLE thread;
DWORD tid;
thread=CreateThread(NULL,0,t1,0,0,&tid);
Sleep(5000); // ждем, пока поток начнет работу
QueueUserAPC(apcfunc,(HANDLE)thread,0);
while (waiting==0);
MessageBox(NULL,"Поток выполнил функцию APC","Debug",MB_OK);
}
Насколько полезной может оказаться технология АРС для прикладного программиста? На самом деле при создании программ редко кто использует АРС напрямую, однако эта технология широко используется многими полезными механизмами Windows, такими как, например, перекрывающийся (overlapped) ввод/ вывод.
Заключение
Если раньше вы не имели дело с многозадачными средами, возможно, вам потребуется некоторое время для того, чтобы изучить тонкости организации многозадачности в Windows, однако, скорее всего, понятия потоков, процессов, нитей и функций АРС не будут казаться вам слишком сложными. Однако, если в прошлом вы работали в других операционных системах, возможно механизмы многозадачности, свойственные Windows, покажутся вам непривычными, странными и может быть даже отталкивающими.
Чтобы спроектировать многопоточное приложение, требуется дополнительное время и усилия, однако преимущества многопоточной технологии очевидны. При помощи многопоточности можно решить множество проблем. Кроме того, часто многопоточная программа работает быстрее, чем ее однопоточный аналог. Это особенно заметно на многопроцессорных компьютерах.
Во итогам всего вышеизложенного материала можно сделать следующие резюме:
Самый простой способ запуска программы
Несмотря на то что Microsoft не рекомендует использовать WinExec, во многих ситуациях этот устаревший вызов является самым удобным способом запуска внешней программы из вашего кода. Чтобы запустить программу, достаточно передать в качестве первого аргумента командную строку. Второй аргумент потребуется в ситуации, если вы хотите установить метод отображения основного окна запускаемой программы. Чаще всего второй аргумент вызова WinExec получает значение SW_SHOW, однако вместо этого можно использовать SW_MINIMIZED, SM_MAXIMIZED или другие подобные константы. Допустимые значения второго аргумента перечислены в определении функции ShowWindow. Имейте в виду, что запускаемая программа может проигнорировать второй аргумент и осуществить отображение собственного главного окна по своему усмотрению.
WinExec не обладает гибкостью CreateProcess, однако этот вызов значительно проще использовать. Если запустить программу не удалось, функция возвращает значение, меньшее 32. В противном случае возвращается дескриптор экземпляра запущенной программы. Пример использования WinExec приведен в листинге 1.
Более сложный способ запуска программ
В некоторых случаях удобнее использовать другой устаревший системный вызов с именем ShellExecute. Этот вызов позволяет направить графической оболочке Windows запрос на открытие (open), изучение (explore) или распечатку (print) некоторого документа или каталога. Функцию ShellExecute можно использовать для запуска любых исполняемых файлов, но вместо этого вы можете передать этой функции имя документа или файла данных, а оболочка сама определит, какую из программ использовать для обработки этого документа или файла. Помимо этого функция ShellExecute позволяет открыть дисковый каталог. Пример использования ShellExecute приведен в листинге 1.
Запуск программ методом для настоящих специалистов
Существуют две основные причины, по которым вы можете использовать функцию CreateProcess для запуска программ. Во-первых, возможно, вам необходим дополнительный контроль над процедурой запуска. Во-вторых, возможно, вы принадлежите к категории людей, которые не ходят проторенными тропами, не любят простых решений и предпочитают справляться с проблемами, используя при этом самые сложные методы.
В качестве аргументов CreateProcess принимает имя программы и командную строку. Имя программы можно включить в состав командной строки. Кроме того, в функцию CreateProcess следует передать две структуры SECURITY_ATTRIBUTES, coдержащие информацию о дескрипторах процесса и потока, возвращаемых этой функцией. Структуры содержат информацию о наследовании дескрипторов дочерними процессами, а также об уровнях доступа к этим дескрипторам. Например, если родительский процесс должен синхронизировать себя с дочерним процессом, ему потребуется уровень доступа SYNCHRONIZE.
Помимо этого при использовании CreateProcess в эту функцию следует передать некоторые управляющие флаги, строки окружения, текущий каталог и структуру, в которой будут размещены дескрипторы нового процесса и потока, а также их идентификаторы ID.
Как видите, прежде чем обратиться к CreateProcess, требуется выполнить немало подготовительной работы. Возможно, если вам нужно всего лишь запустить Notepad, чтобы отобразить на экране содержимое файла README.TXT, будет удобнее использовать WinExec или ShellExecute, о которых мы говорили ранее. Пример использования CreateProcess приведен в листинге 3.
Ожидание завершения программы, работа с кодом завершения
Если вы хотите, чтобы ваша программа ожидала завершения работы какого-либо другого процесса, вы можете воспользоваться как минимум двумя способами. Прежде всего можно использовать вызов GetExitCodeProcess, который служит для определения кода завершения процесса. Код завершения процесса — это простое число, значение которого устанавливается процессом чаще всего для того, чтобы сообщить внешнему миру об успехе или неудаче своего выполнения.
Процесс может установить значение кода завершения в момент завершения своей работы. Для этого можно использовать вызовы ExitProcess или TerminateProcess. Конечно же, консольная программа может обратиться к exit или вернуть значение функции main или WinMain. Кроме того, значение кода завершения может быть установлено в результате возникновения неподдерживаемого программой исключения.
Функция GetExitCodeProcess читает значение кода завершения процесса (чтобы воспользоваться этим вызовом, вы должны обладать уровнем доступа PROCESS_ QUERY_INFORMATION к дескриптору процесса). Если процесс все еще продолжает работать, вместо кода завершения функция возвращает значение STILL_ACTIVE.
Другой способ ожидания завершения программы подразумевает использование вызова WaitForSingleObject, которому передается дескриптор процесса (для этого требуется уровень доступа SYNCHRONIZE). По вашему желанию вызов может проверить состояние процесса и немедленно вернуть управление основной программе, также он может ожидать завершения процесса в течение некоторого устанавливаемого вами интервала времени. Наконец, вызов может ожидать завершения процесса неограниченное время. Пример использования этого вызова приведен в листинге 3.
Существует множество других методов работы с процессами. Соответствующие вызовы перечислены в табл. 3.
Создание потока при помощи Windows API
Самым низкоуровневым вызовом, осуществляющим создание нового программного потока, является вызов CreateThread. Однако этот вызов не осуществляет подготовительных процедур, настраивающих используемую вами библиотеку (C++ или MFC), на работу в многопоточной среде. Чтобы выполнить все подготовительные процедуры, а затем создать новый поток, следует воспользоваться специальным вызовом, входящим в состав соответствующей библиотеки.
Однако, если когда-либо вам потребуется воспользоваться CreateThread, вы можете сделать это без каких-либо затруднений. Пример использования этой функции содержится в листинге 7. Вызовы, предназначенные для работы с потоками, перечислены в табл. 4.
Создание нового потока при помощи стандартной библиотеки C++
Стандартная библиотека C++ поддерживает вызовы _beginthread и _beginthreadex. При обращении к этим вызовам происходит подготовка библиотеки к функционированию в многопоточной среде, после чего создается новый программный поток. При использовании этих вызовов следует также настроить проект на использование многопоточной версии библиотеки. Для этого на вкладке C/C++ в категории Code Generation (Генерация кода) при помощи списка Use Run Time Library (Используемая библиотека времени исполнения) выберите любую многопоточную библиотеку. Оба вызова возвращают дескриптор нового потока, который можно использовать при обращении к вызовам, предназначенным для работы с потоками.
Функция _beginthreadex -используется в случае, если необходимо запустить поток в приостановленном состоянии, указать атрибуты безопасности или получить идентификатор ID-потока.
Чтобы остановить работу потока, запущенного при помощи _beginthread, обратитесь к функции _endthread. Если для создания потока использовалась функция _beginthreadex, для завершения потока следует использовать функцию _endthreadex. Функция _endthread автоматически закрывает дескриптор потока, однако функция _endthreadex этого не делает. Поэтому при использовании _endthreadex следует закрыть дескриптор самостоятельно при помощи функции CloseHandle.
Отображение окон поверх других окон
В Windows существует понятие потока переднего плана (это поток, владеющий окном, расположенным на переднем плане по отношению к другим окнам процесса). Такой поток называют английским термином foreground thread. Когда другие потоки создают новые окна, эти окна могут появиться на заднем плане, вместо того чтобы возникнуть на переднем плане.
Чтобы исправить такое положение вещей, можно воспользоваться функцией SetForegroundWindow, которая переносит новое окно на передний план. Однако, если вы хотите отобразить простое информационное окошко (message box), вы можете столкнуться с проблемой. Дело в том, что окна типа MessageBox создаются и уничтожаются операционной системой фактически без вашего участия, поэтому ваша программа не может получить дескриптор окна типа MessageBox. Чтобы разместить окно MessageBox на переднем плане, необходимо использовать при его создании флаг MB_SETFOREGROUND.
Получение дескрипторов процесса и потока
Если вы хотите работать с процессом или потоком, вы должны обладать его дескриптором. Мало того, вы должны обладать необходимым уровнем доступа. Проще всего получить необходимый дескриптор с необходимым уровнем доступа при помощи вызова CreateProcess. Эта функция возвращает вызывающей программе дескрипторы нового процесса и соответствующего ему потока, при этом программе предоставляются необходимые права доступа.
Что делать, если вы хотите управлять процессом, который вы сами не создавали? Для этого вам потребуется идентификатор ID этого процесса. Функция OpenProcess позволяет, исходя из ID процесса, определить его дескриптор. Этот вызов может оказаться полезным в случае, если вы хотите управлять процессом, который был создан из другого процесса. Дело в том, что передавать дескриптор процесса из процесса в процесс нельзя. Вместо этого допускается передача идентификатора ID процесса. Получив ID процесса, передайте его функции OpenProcess. Сообщите также необходимые права доступа. Функция вернет дескриптор процесса, обладающего указанным идентификатором ID.
Работа с потоками несколько отличается от работы с процессами. Если вы хотите предоставить другому процессу возможность доступа к вашему потоку, вы должны выполнить два вызова. Сначала при помощи функции GetCurrentThread необходимо получить специальный дескриптор потока. Этот дескриптор необходимо передать функции DupUcateHandle, которая создаст дескриптор, который можно передавать другим процессам.
Ожидание завершения потока, работа с кодом завершения
Чтобы подождать завершения работы потока, можно воспользоваться как минимум двумя способами. Во-первых, можно использовать функцию GetExi'tCodeThread. Основное назначение этого вызова — получение кода завершения потока. Код завершения потока — это число, обычно используемое для оповещения других программ об успехе или неудаче выполнения потока.
Поток может установить значение собственного кода завершения при помощи функций ExitThread или TerminateThread. Конечно же, код завершения можно вернуть в качестве возвращаемого значения главной функции потока.
Функция GetExitCodeThread возвращает код завершения потока, завершившего свою работу (для получения кода вы должны обладать уровнем доступа THREAD_ QUERY_INFORMATION). Однако, если в момент обращения к этой функции поток все еще продолжает функционировать, функция возвращает значение STILL_ACTIVE.
Второй способ ожидания завершения работы подразумевает использование функции WaitForSingleObject в отношении дескриптора потока (для этого нужно обладать уровнем доступа SYNCHRONIZE). В зависимости от вашего желания эта функция может либо проверить текущее состояние потока и немедленно вернуть управление вызвавшей программе, либо ожидать завершения потока в течение заранее установленного промежутка времени, либо ожидать завершения потока неограниченное время. Пример использования этой функции приведен в листинге 3.
Вызовы, которые вы можете использовать для работы с потоками, перечислены в табл. 5
Члены класса CwinThread Таблица 5
Член | Описание |
CreateThread | Создает поток. Вместо этого используйте AfxCreateThread |
m_pMainWnd | Основное окно потока |
m_pActiveWnd | Текущее активное окно потока |
m_bAutoDelete | Если равно «истине», после завершения работы потока объект автоматически уничтожается. По умолчанию равно «истине» |
m_hThread | Дескриптор потока |
m_nThreadID | Идентификатор ID потока |
GetThreadPriority | Возвращает уровень приоритета потока |
SetThreadPriority | Устанавливает уровень приоритета потока |
SuspendThread | Приостанавливает выполнение потока |
ResumeThread | Продолжает выполнение потока |
PostThreadMessage | Помещает сообщение в очередь событий потока |
Initlnstance | Эту функцию необходимо переопределить для того, чтобы определить новый инициализационный код потока |
Run | Эту функцию необходимо переопределить для того, чтобы перенастроить цикл обработки сообщений потока |
PreTranslateMessage | Фильтрует сообщения и обрабатывает нажатия на «горячие» клавиши |
PumpMessage | Низкоуровневый модуль получения сообщений |
Onldle | Вызывается в случае, если сообщения отсутствуют |
IsIdleMessage | Проверяет наличие специальных сообщений MFC |
Exitlnstance | Содержит код завершения работы потока. Вы можете переопределить эту функцию |
ProcessWndProcException | Обрабатывает неподдерживаемые исключения |
ProcessMessageFilter | Фильтрует сообщения |
GetMainWnd | Возвращает указатель на основное окно потока |
Использование локальной памяти потока
Локальная память потока (Thread Local Storage, TLS) обеспечивает хранение переменных, являющихся собственностью этого потока. Каждый процесс может иметь до 64 таких переменных. Каждой переменной TLS соответствует таблица с количеством строк, соответствующих количеству потоков, принадлежащих процессу. В строке, соответствующей некоторому потоку процесса, хранится значение переменной TLS, соответствующее этому потоку. Таблицу TLS можно выделить в памяти при помощи функции TlsAlloc. Эта функция резервирует индекс, который хранится в глобальной переменной. Индекс идентифицирует значение, являющееся глобальным и вместе с тем уникальным для данного потока.
Если поток желает получить доступ к такой переменной, он обращается к TlsSetValue или TlsGetValue, передавая этим вызовам индекс, возвращаемый функцией TlsAlloc. Это значит, что для создания переменной TLS функцию TlsAlloc необходимо вызвать один раз, в самом начале вашего кода. После этого каждый поток будет использовать один и тот же индекс для доступа к своей собственной переменной TLS.
Если переменная TLS больше не нужна, вы можете освободить память, передав индекс переменной в функцию TlsFree. При этом в дальнейшем индекс может использоваться для идентификации другой переменной TLS. Пример использования TLS приведен в листинге 5.
Использование нитей
Если вы хотите, чтобы потоки вашего процесса взаимодействовали между собой, вы должны воспользоваться методами синхронизации (след. лаб. работа). Однако в некоторых случаях проще использовать механизм, напоминающий потоки, однако позволяющий различным фрагментам кода контролировать передачу управления от одного из них к другому. Именно для этого предназначены нити.
Создать самую первую нить нельзя. Вместо этого вы должны преобразовать в нить один из существующих потоков. Это выполняется при помощи функции C
Дата публикования: 2015-07-22; Прочитано: 647 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!