Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Создание эффективных приложений для Windows Джеффри Рихтер 2004 (Книга).pdf
Скачиваний:
375
Добавлен:
15.06.2014
Размер:
8.44 Mб
Скачать

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

завершения его потока Серверный поток, получив от клиента специальный запрос, выходит из своего цикла и сразу же завершается

Я предпочел сделать так, чтобы первичный поток ждал завершения серверного вызовом WаittForMultipleObjects, - просто из желания продемонстрировать, как исполь зуется эта функция На самом делс я мог бы вызвать и WaitForStngleObject, передав ей описатель серверного потока, и все работало бы точно так же

Как только первичный поток узнает о завершении серверного, он трижды вызы вает CloseHandle для корректного закрытия всех объектов ядра, которые использова лись программой Конечно, система могла бы закрыть их за меня, но как-то спокой нее, когда делаешь это сам Я предпочитаю полностью контролировать все, что про исходит в моих программах

Ожидаемые таймеры

Ожидаемые таймеры (waitahle timers) ~ это объекты ядра, которые самостоятельно переходят в свободное состояние в определенное время или через регулярные про межутки времени. Чтобы создать ожидаемый таймер, достаточно вызвать функцию

CreateWaitableTimer.

HANDLE CreateWaitableTimer( PSECURITY_ATTRIBUTES psa, BOOL fManualReset, PCTSTR pszName);

О параметрахр psa и pszName я уже рассказывал в главе 3. Разумеется, любой про цесс может получить свой («процессо-зависимый») описатель существующего объек та "ожидаемый таймер", вызвав OpenWaitableTimer.

HANDLE OpenWaitableTirrer( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName);

По аналогии с событиями параметр fManualReset определяет тип ожидаемого тай мера: со сбросом вручную или с автосбросом. Когда освобождается таймер со сбро сом вручную, возобновляется выполнение всех потоков, ожидавших этот объект, а когда в свободное состояние переходит таймер с автосбросом — лишь одного из потоков.

Объекты «ожидаемый таймер» всегда создаются в занятом состоянии. Чтобы со общить таймеру, в какой момент он должен перейти в свободное состояние, вызови те функцию

SetWaitableTimer.

BOOL SetWaitableTimer( HANDLE hTimer, const LARGE_INTEGER *pDueTime, LONG lPeriod, PTIMERAPCROUTINE pfnCompletionRoutine, PVOID pvArgToCotnpletionRoutine, BOOI fResume);

Эта функция принимает несколько параметров, в которых легко запутаться Оче видно, что hTimer определяет нужный таймер. Следующие два параметра (pDиеТiте и lPeriod) используются совместно, первый из них задает, когда таймер должен сра ботать в первый раз, второй определяет, насколько часто это должно происходить в дальнейшем. Попробуем для примера установить таймер так, чтобы в первый раз он сработал 1 января 2002 года в 1:00 PM, а потом срабатывал каждые 6 часов.

// объявляем свои локальные переменные

HANDLE hTimer;

SYSTEMTIME st;

FILETIME ftLocal, ftUTC;

LARGE_INTEGER liUTC;

// создаем таймер с автосбросом

hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

//таймер должен сработать в первый раз 1 января 2002 года в 1:00

PM

//но местному времени

st.wYear = 2002; // год st.wMonth = 1; // январь

st.wOayOfWeek = 0; // игнорируется st.wDay = 1, // первое число месяца st.wHour = 13; // 1 PM

st.wMinute = 0; // 0 минут st.wSecond = 0, // 0 секунд

st.wMilliseconds = 0; // 0 миллисекунд

SystemTimeToFileTime(&st, &ftLocal);

//преобразуем местное время в UTC-время

LocalFileTimeToFilelime(&ttLocal, &ftUTC);

//преобразуем FILETIME в LARGE_INTEGER из-за различий в выравнивании данных

liUTC.LowPart = ftUTC dwLowDateTime; liUTC.HighPart = ftUTC dwHighDateTime;

//устанавливаем таймер

SetWaitablcTimer(hTimer, &liUTC, 6 * 60 * 60 * 1000, NULL, NULL, FALSE);

...

Этот фрагмент кода сначала инициализирует структуру SYSTEMTIME, определяя время первого срабатывания таймера (его перехода в свободное состояние). Я уста новил это время как местное. Второй параметр представляется как const LARGE_IN TEGER * и поэтому нс позволяет напрямую использовать структуру SYSTEMTIME. Од нако двоичные форматы структур FILETIME и LARGE_INTEGER идентичны: обе содер жат по два 32-битных значения. Таким образом, мы можем преобразовать структуру SYSTEMTIME в FILETIME. Другая проблема заключается в том, что функция SetWaitable Timer ждет передачи времени в формате UTC (Coordinated Universal Time). Нужное преобразование легко исуществляется вызовом LocalFileTimeToFileTime

Поскольку двоичные форматы структур FILETIMF, и IARGE_INTEGER идентичны, у Вас может появиться искушение передать в SetWaitableTimer адрес структуры FILETIME напрямую;

// устанавливаем таймер

SetWaitableTimer(hTirner, (PLARGE^INTEGER) &ftUTC, 6 * 60 * 60 * 1000, NULL, NULL, FALSE);

В сущности, разбираясь с этой функцией, я так и поступил. По это большая ошиб ка! Хотя двоичные форматы структур FILETIME и LARGE_INTEGER совпадают, вырав нивание этих структур осуществляется по-разному. Адрес любой структуры FILETIME должен начинаться на 32-битной границе, а адрес любой структуры IARGE_INTEGER — на 64битной. Вызов SetWaitableTimer с передачей ей структуры FILETIME может cpa

ботать корректно, но может и не сработать — все зависит от того, попадет ли начало структуры FlLETIME на 64-битную границу. В то же время компилятор гарантирует, что структура LARGE_INTEGER всегда будет начинаться на 64-битной границе, и по этому правильнее скопировать элементы FILETIME в элементы LARGE_INTEGER, а за тем передать в SetWaitableTtmer адрес именно структуры LARGE_INTEGER.

NOTE:

Процессоры x86 всегда «молча» обрабатываю ссылки на невыровненные дан ные. Поэтому передача в SetWaitableTimer адреса структуры FILETIME будет сра батывать, если приложение выполняется на машине с процессором x86 Од нако другие процессоры (например, Alpha) в таких случаях, как правило, ге нерируют исключение EXCEPTION_DATATYPE_MISALIGNMENT, которое приво дит к завершению Вашего процесса Ошибки, связанные с выравниванием дан ных, — самый серьезный источник проблем при переносе на другие процес сорные платформы программного кода, корректно работавшего на процессо рах x86 Так что, обратив внимание на проблемы выравнивания данных сей час, Вы сэкономите себе месяцы труда при переносе программы на другие платформы в будущем! Подробнее о выравнивании данных см. главу 13.

Чтобы разобраться в том, как заставить таймер срабатывать каждые 6 часов (на чиная с 1:00 PM 1 января 2002 года), рассмотрим параметр lPeriod функции SetWaitable Timer. Этот параметр определяет последующую частоту срабатывания таймера (в мс). Чтобы установить 6 часов, я передаю значение, равное 21 600 000 мс (т e. 6 часов * 60 минут • 60 секунд • 1000 миллисекунд).

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

Следующий код демонстрирует, как установить таймер на первое срабатывание через 5 секунд после вызова SetWaitableTimer.

//объявляем свои локальные переменные

HANDLF hTimer; LARGE_INTEGER li;

// создаем таймер с автосбросом

hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

//таймер должен сработать через 5 секунд после вызова

SetWaitableTimer;

//задаем время в интервалах по 100 нс

const int nTimerUnitsPerSecond = 10000000;

// делаем полученное значение отрицательным, чтобы

SetWaitableTimer