Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых
<

Программирование для Windows NT

© Александр Фролов, Григорий Фролов
Том 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр.
Рецензия PC WEEK

[Назад] [Содеожание] [Дальше]

Запуск задач

Существует три способа запустить задачу в приложениях, составленных на языке программирования C.

Во-первых, можно использовать фукнцию CreateThread XE "CreateThread" , которая входит в программный интерфейс операционной системы Microsoft Windows NT. Этот способ предоставляет наибольшие возможности по управлению запущенными задачами, позволяя, в частности, присваивать запущенным задачам атрибуты защиты и создавать задачи в приостановленном состоянии.

Во-вторых, в вашем распоряжении имеется функция из библиотеки системы разработки Microsoft Visual C++ с названием _beginthread XE "_beginthread" . Задачи, созданные с использованием этой функции, могут обращаться ко всем стандартным функциям библиотеки и к переменной errno XE "errno" .

И, наконец, в-третьих, можно запустить задачу при помощи функции _beginthreadex XE "_beginthreadex" , которая определена в библиотеке Microsoft Visual C++, но имеет возможности, аналогичные функции CreateThread XE "CreateThread" .

Мы рассмотрим все эти способы.

Функция CreateThread XE "CreateThread"

Прототип функции CreateThread XE "CreateThread" , с помощью которой процессы могут создавать задачи, представлен ниже:


HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes,// атрибуты защиты 
  DWORD dwStackSize,       // начальный размер стека в байтах 
  LPTHREAD_START_ROUTINE lpStartAddress,// адрес функции 
                                        // задачи 
  LPVOID  lpParameter,           // параметры для задачи 
  DWORD   dwCreationFlags,       // параметры создания задачи 
  LPDWORD lpThreadId);           // адрес переменной для 
                                 // идентификатора задачи

Через параметр lpThreadAttributes передается адрес структуры SECURITY_ATTRIBUTES, определяющей атрибуты защиты для создаваемой задачи, или значение NULL. В последнем случае для задачи будут использованы атрибуты защиты, принятые по умолчанию. Это означает, что идентификатор созданной задачи можно использовать в любых функциях, выполняющих любые операции над задачами. Указывая атрибуты защиты, вы можете запретить использование тех или иных функций.

Приведем структуру SECURITY_ATTRIBUTES:


typedef struct _SECURITY_ATTRIBUTES 
{ 
  DWORD  nLength;              // размер структуры в байтах
  LPVOID lpSecurityDescriptor; // указатель на дескриптор 
                               //   защиты
  BOOL   bInheritHandle;       // флаг наследования 
                               //   идентификатора
} SECURITY_ATTRIBUTES;

При подготовке структуры в поле nLength следует записать размер структуры SECURITY_ATTRIBUTES XE "SECURITY_ATTRIBUTES" .

Поле указателя на дескриптор защиты lpSecurityDescriptor не заполняется приложением непосредственно. Вместо этого для установки дескритора защиты используется набор функций, которым в качестве одного из параметров передается указатель на структуру SECURITY_ATTRIBUTES XE "SECURITY_ATTRIBUTES" . Эти функции подробно описаны в SDK. В нашей книге для экономии места мы не будем на них останавливаться. Система защиты Microsoft Windows NT достаточно мощная и потому заслуживает отдельного рассмотрения.

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

Таким образом, первые два параметра функции CreateThread не вызывают затруднений. Они могут быть в большинстве случаев указаны как NULL и 0, соответственно.

Параметр lpStartAddress задает адрес функции, которая будет выполняться как отдельная задача. Здесь вы можете просто указать имя этой функции. Функция задачи имеет один 32-разрядный параметр и возвращает 32-разрядное значение. Указанный параметр передается функции CreateThread XE "CreateThread" через параметр lpParameter.

Если значение параметра dwCreationFlags равно нулю, после вызова функции CreateThread задача немедленно начнет свое выполнение. Если же в этом параметре указать значение CREATE_SUSPENDED XE "CREATE_SUSPENDED" , задача будет загружена, но приостановлена. Возобновить выполнение приостановленной задачи можно будет позже с помощью функции ResumeThread XE "ResumeThread" .

И, наконец, через параметр lpThreadId вы должны передать адрес переменной типа DWORD, в которую бедет записан системный номер созданной задачи (thread identifier).

В случае успеха функция CreateThread возвращает идентификатор задачи (thread handle), пользуясь которым можно выполнять над задачей те или иные операции. Не путайте этот идентификатор с системным номером задачи. При ошибке функция CreateThread возвращает значение NULL.

Ниже мы приведи пример использования функции CreateThread:


hThread = CreateThread(NULL, 0, 
  (LPTHREAD_START_ROUTINE)ThreadRoutine,
  (LPVOID)hwndChild, 0,(LPDWORD)&dwIDThread);

Здесь мы не используем атрибуты защиты и устанавливаем размер стека запускаемой задачи, равным размеру стека главной задачи процесса.

В качестве функции задачи мы использовали функцию с именем ThreadRoutine, имеющую следующий вид:


DWORD ThreadRoutine(HWND hwnd) 
{
  . . .
  // Оператор return завершает выполнение задачи
  return 0;
}

Эта функция имеет один параметр, который в нашем случае будет принимать значение hwndChild.

Наша функция задачи завершает свое выполнение оператором return, хотя, как вы увидите дальше, есть и другие способы. Для проверки значения, возвращенного функцией задачи, процесс может воспользоваться функцией GetExitCodeThread XE "GetExitCodeThread" , которая будет описана позже.

Так как при запуске задачи мы указали значение параметра dwCreationFlags, равное нулю, сразу после запуска задача начнет свою работу. Системный номер созданной задачи будет записан в переменную dwIDThread.

Функция _beginthread

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

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

Для решения этой проблемы в системе разработки Microsoft Visual C++ предусмотрены отдельные библиотеки для создания однозадачных и мультизадачных приложений.

Для выбора правильной библиотеки для проекта Microsoft Visual C++ версии 4.0 или 4.1 выберите из меню Build строку Settings. На экране появится блокнот настройки параметров проекта Project Settings. Открыв страницу C/C++ этого блокнота, выберите в списке Category строку Code Generation. После этого на странице появится список Use run-time library, в котором имеются следующие строки:

·       Single-Treaded;

·       Multithreaded;

·       Multithreaded DLL;

·       Debug Single-Treaded;

·       Debug Multithreaded;

·       Debug Multithreaded DLL

Если ваше приложение является однозадачным, имеет смысл выбрать библиотеку Single-Treaded или Debug Single-Treaded (при отладке). Функции из этой библиотеки будут работать быстрее, чем из библиотеки Multithreaded (которую тоже можно использовать в однозадачных приложениях), так как не будет накладных расходов на мультизадачность.

В том случае, если вы создаете мультизадачное приложение, необходимо использовать библиотеки Multithreaded и Multithreaded DLL (для создания мультизадачных библиотек DLL) либо отладочные версии этих библиотек.

Заметим, что для однозадачных приложений используется библиотека с именем libc.lib, а для мультизадачных - с именем libcmt.lib.

После того как вы указали мультизадачную библиотеку, вы можете использовать функции _beginthread XE "_beginthread" и _beginthreadex XE "_beginthreadex" для запуска задач. Приведем прототип функции _beginthread, описанный в файле process.h XE "process.h" :


unsigned long _beginthread(
  void(*StartAddress)(void*), // адрес функции задачи
  unsigned uStackSize,    // начальный размер стека в байтах
  void     *ArgList);     // параметры для задачи

Заметим, что функция задачи, запускаемой при помощи функции _beginthread XE "_beginthread" , не возвращает никакого значения. Ее адрес передается функции _beginthread через параметр StartAddress. Через параметр ArgList вы можете передать функции задачи один параметр.

Начальный размер стека, выделяемого задаче, указывается через параметр uStackSize. Так же как и в случае с функцией CreateThread XE "CreateThread" , для размера стека можно указать нулевое значение. При этом для задачи создается стек такого же размера, что и для главной задачи процесса.

В случае успеха функция _beginthread XE "_beginthread" возвращает идентификатор запущенной задачи. Если же произошла ошибка, возвращается значение -1.

Приведем пример использования функции _beginthread XE "_beginthread" :


_beginthread(ThreadRoutine, 0, (void*)(Param));

Здесь запускается задача, функция которой имеет имя ThreadRoutine. Ей передается в качестве параметра значение Param.

Функция ThreadRoutine должна выглядеть следующим образом:


void ThreadRoutine(void *Param) 
{
  . . .
  _endthread();
}

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

Функция _beginthreadex

В том случае, если вам нужны возможности функции CreateThread (например, необходимо создать задачу в приостановленном состоянии) и вместе с тем необходимо использовать функции библиотеки транслятора, имеет смысл обратить внимание на функцию_beginthreadex. Прототип этой функции мы привели ниже:


unsigned long _beginthreadex(
  void     *Security,       // указатель на дескриптор защиты
  unsigned StackSize,              // начальный размер стека
  unsigned (*StartAddress)(void*), // адрес функции задачи
  void     *ArgList,             // параметры для задачи
  unsigned Initflag,             // параметры создания задачи
  unsigned *ThrdAddr);           // адрес созданной задачи

Для запуска задачи в приостановленном состоянии через параметр Initflag необходимо передать значение CREATE_SUSPENDED XE "CREATE_SUSPENDED" .

Функция задачи, которая запускается с помощью функции _beginthread ex, имеет один параметр и возвращает 32-разрядное значение, аналогично функции задачи, запускаемой функцией CreateThread XE "CreateThread" . Для завершения своего выполнения функция задачи должна использовать либо оператор возврата, либо функцию _endthreadex, не имеющую параметров.

В случае успеха функция _beginthread ex возвращает идентификатор запущенной задачи. Если же произошла ошибка, возвращается значение 0 (а не -1, как это было для функции _beginthread).

[Назад] [Содеожание] [Дальше]