Статья:
Safe Win32 TimerАвтор(ы): Приходько Михаил aka Crusader Mike
Авторы:
Приходько Михаил aka Crusader Mike
Аннотация:
этот класс является источников "безопасных" таймерных нотификаций. Т.е. он не обладает гениальным недостатком обычного таймера, который оставляет WM_TIMER сообщения в очереди после вызова KillTimer. Этот класс должен быть использован и удален только из потока, т.к. он создает и использует окошко.
multimedia timers намного быстрее (плавное перемещение несколких Common Controls с перерисовкой на iP-200:-) под WinXP:-0 с включёнными Visual Styles 8-) выдаёт 30~40fps, приоритет процесса и потока — Normal) и точнее (разрешение 5 ms — не тормозит и вполне достаточно для UI), а использование почти ничем не отличается от использования тормозных сообщений WM_TIMER, толко вместо обработки WM_TIMER нужно написать callback функцию.
таймер создаваемый функцией CreateTimer вообще по своей природе не стабилен (это не таймер а вообще хрень какято) так как он эмулируется из настоящих таймеров ядра подсистемой User32. Которая стоит в данном случае над ядром. Именно по этому я отказался от использования данного таймера в серъезных целях. Да еще и к томуже способ сообщения т.е. посылка события оставляет желать лучшего. Вместо этого я бы вам предложил посмотреть функцию CreateWaitableTimer которая предостовляет реальные таймеры (ну почти), ну а если мы пишем под NT(2000) то весьма целесообразно будет использовать функции NtCreateTimer,NtSetTimer,NtCancelTimer,NtOpenTimer представленные в NtDll.dll (но к сожалению не документированые).
С Уважением Mr.Orbit <mrorbit@mail.ru>
И под Win98. А под Win95 — нету...
А FlushInstructionCache под 9x есть, но ничего не делает. IMHO, этот код писался не для 9x все-таки.
А касаемо способа работы с таймерами, есть такая идея. Во-первых, пусть это каждый решает для себя, в меру своей крутости, понтов и т.п. А во-вторых, чтобы это можно было реализовать, делается следующее: из CTimerHost выдирается весь специфический код (то есть большая часть SetTimer, большая часть KillTimer, m_hWnd и тот самый PostMessage, который, видимо, не понравился Анониму) и помещается в CReceiver. Дальше на совести использующего лежит написание такого CReceiver, который ему нужен (можно сделать иерархию с интерфейсом, скажем, IReceiver, в корне). А CTimerHost переделывается в шаблон, параметром в котором выступает этот самый CReceiver. Дальше там, где стояли определения переменных CTimerHost, мы ставим определения CTimerHost<CMyReceiver> и радуемся жизни.
timeSetEvent() есть даже в Win95. Или он тоже не настоящий?
Здравствуйте, Приходько Михаил aka Crusader Mike, Вы писали:
ПМA>этот класс является источников "безопасных" таймерных нотификаций. Т.е. он не обладает гениальным недостатком обычного таймера, который оставляет WM_TIMER сообщения в очереди после вызова KillTimer. Этот класс должен быть использован и удален только из потока, т.к. он создает и использует окошко.
Чет слишком сложный таймер у автора получился. Предлагаю более простой вариант без создания окон, использования MFC, объектов (не люлю я их), фанков и прочих жутких вещей. Смысл предлагаемого метода — для одного таймера использовать несколько идентификаторов, которые при удалении таймера сменяют друг друга. Таким образом застрявшее в очереди сообщение WM_TIMER (в 99.99% случаев оно будет одно) не будет иметь текущий идентификатор и, следовательно, не будет обработано. Заодно (бонус!

) реализуется проверка на такую неприятную в большинстве случаев вещь, как рекурсивный вызов обработчика таймера (я думаю, все видели проги, генерящие по таймеру кучу одинаковых сообщений об ошибке

).
Единственное пояснение. В случае необходимости можно переопределить SAFE_TIMER_ID_BITS. Оно определяет количество битов для идентификаторов таймера. 1 бит = 2 идентификатора, 2 бита = 4 идентификатора и т.д. В большинстве случаев хватит 1 бита, а ситуацию когда не хватит трех с трудом могу представить. Это наверное будет плохо спроектированное приложение.
// SafeTimer.h
#pragma once
#ifndef SAFE_TIMER_ID_BITS
#define SAFE_TIMER_ID_BITS 1
#endif
#define SAFE_TIMER_ID_MASK UINT_PTR(((1 << (SAFE_TIMER_ID_BITS)) - 1) << 1)
#define SAFE_TIMER_BUSY_BIT UINT_PTR(1)
DECLARE_HANDLE(SAFE_TIMER);
inline SAFE_TIMER InitSafeTimer(UINT_PTR uTimerId)
{
return SAFE_TIMER(uTimerId << ((SAFE_TIMER_ID_BITS) + 1));
}
inline bool IsSafeTimerId(SAFE_TIMER SafeTimer, WPARAM wParam)
{
return (UINT_PTR(SafeTimer) & ~SAFE_TIMER_BUSY_BIT) == UINT_PTR(wParam);
}
inline bool IsSafeTimerFunctionBusy(SAFE_TIMER *pSafeTimer)
{
if (*PUINT_PTR(pSafeTimer) & SAFE_TIMER_BUSY_BIT)
{
return true;
}
*PUINT_PTR(pSafeTimer) |= SAFE_TIMER_BUSY_BIT;
return false;
}
inline void SafeTimerFunctionComplete(SAFE_TIMER *pSafeTimer)
{
*PUINT_PTR(pSafeTimer) &= ~SAFE_TIMER_BUSY_BIT;
}
inline UINT_PTR SetSafeTimer(HWND hWnd, SAFE_TIMER SafeTimer, UINT uElapse, TIMERPROC lpTimerFunc)
{
_ASSERT(hWnd != NULL);
return SetTimer(hWnd, UINT_PTR(SafeTimer) & ~SAFE_TIMER_BUSY_BIT, uElapse, lpTimerFunc);
}
inline BOOL KillSafeTimer(HWND hWnd, SAFE_TIMER *pSafeTimer)
{
BOOL b = KillTimer(hWnd, *PUINT_PTR(pSafeTimer) & ~SAFE_TIMER_BUSY_BIT);
#if (SAFE_TIMER_ID_BITS) == 1
*PUINT_PTR(pSafeTimer) ^= 2;
#else
*PUINT_PTR(pSafeTimer) = ((*PUINT_PTR(pSafeTimer) + 2) & SAFE_TIMER_ID_MASK)
| (*PUINT_PTR(pSafeTimer) & ~SAFE_TIMER_ID_MASK);
#endif
return b;
}
#undef SAFE_TIMER_ID_BITS
#undef SAFE_TIMER_ID_MASK
#undef SAFE_TIMER_BUSY_BIT
Пример использования в оконной процедуре:
const UINT_PTR IDR_DIALOG_UPDATE = 1;
const UINT_PTR IDR_UNINST_WIN_PISTA = 2;
const UINT_PTR IDR_KILL_S_BALMER = 3;
.
.
SAFE_TIMER m_stDialogUpdate = InitSafeTimer(IDR_DIALOG_UPDATE);
.
.
SetSafeTimer(hwndDlg, m_stDialogUpdate, DIALOG_UPDATE_FREQUENCY, 0);
.
.
KillSafeTimer(hwndDlg, &m_stDialogUpdate);
.
.
case WM_TIMER:
if (IsSafeTimerId(m_stDialogUpdate, wParam))
{
if (!IsSafeTimerFunctionBusy(&m_stDialogUpdate))
{
// ...
SafeTimerFunctionComplete(&m_stDialogUpdate);
}
return TRUE;
}
break;
Пример использования в функции:
const UINT_PTR IDR_DIALOG_UPDATE = 1;
const UINT_PTR IDR_UNINST_WIN_PISTA = 2;
const UINT_PTR IDR_KILL_S_BALMER = 3;
.
.
SAFE_TIMER m_stDialogUpdate = InitSafeTimer(IDR_DIALOG_UPDATE);
.
.
SetSafeTimer(hwndDlg, m_stDialogUpdate, DIALOG_UPDATE_FREQUENCY, OnDialogUpdate);
.
.
KillSafeTimer(hwndDlg, &m_stDialogUpdate);
.
.
void CALLBACK OnDialogUpdate(HWND, UINT, UINT_PTR, DWORD)
{
if (IsSafeTimerFunctionBusy(&m_stDialogUpdate))
{
return;
}
// ...
SafeTimerFunctionComplete(&m_stDialogUpdate);
}