Как создать таймер в c
Перейти к содержимому

Как создать таймер в c

  • автор:

Таймер в Си

Таймер должен через время положить файл на БД. [Таймер]
Мне нужен таймер, а я не знаю как его делать именно, чтоб в конкретное время он сохранял информацию.

Поясните за таймер!) Или таймер на формах наследниках
Доброго. В проге есть основная форма и в процессе работы создается 1-2-3-4-5 форм наследников все.

Таймер похожий на таймер в delphi
Добрый всем)В общем хочу создать класс таймера похожий на класс таймера на delphi.Суть в том,что я.

Таймер, вложенный в таймер
Доброго времени суток. Я новичек, С знаю плохо. Сделал небольшой проект на Arduino, но остался.

Программирование на C, C# и Java

Уроки программирования, алгоритмы, статьи, исходники, примеры программ и полезные советы

ОСТОРОЖНО МОШЕННИКИ! В последнее время в социальных сетях участились случаи предложения помощи в написании программ от лиц, прикрывающихся сайтом vscode.ru. Мы никогда не пишем первыми и не размещаем никакие материалы в посторонних группах ВК. Для связи с нами используйте исключительно эти контакты: vscoderu@yandex.ru, https://vk.com/vscode

Исходный код: таймер на C# в Windows Forms

Разбираем создание таймера на языке программирования C# в приложении Windows Forms. Полный исходный код с подробными комментариями можно будет скачать внизу страницы.

Для начала в Windows Forms создаём внешнюю оболочку программы. У нас она выглядит вот так:

таймер на C# в Windows Forms - vscode.ru

Здесь у нас 8 Label’ов, 3 TextBox’a, 3 Buttom’a и сам Timer.

Примечание: при переносе элемента Timer в форму, на неё ничего не появляется. Лишь в нижней части окна программы под формой появляется значок , не пугайтесь.

Щёлкнем на значок таймера и в окне «Свойства» в группе «Поведение» устанавливаем значение параметра Interval равным 1000. Данный параметр определяет длину тика таймера в миллисекундах, указав 1000, мы сделали один тик равным одной секунде.

После оформления и настройки приступаем к коду. Вводим целочисленные переменные h — часы, m- минуты, s — секунды.

Затем дважды щёлкаем мышью на кнопке «Старт» и переходим на участок кода, отвечающий за клик на эту кнопку.

Туда мы пишем следующий код:

Объектно-ориентированное программирование на Си без плюсов. Часть 2. Таймер

Время это одно из измерений окружающего нас мира, а любая определяемая сущность или находится (условно) в состоянии покоя, или изменяет своё состояние в течение времени. И, естественно если нет возможности контроля времени, что-то существенное сварганить не получится. Поэтому первое, что должно быть — это решение в виде таймера.

Для Linux в Си в настоящее время есть следующие функции:

unsigned int sleep(unsigned int __seconds); ,

объявленная в файле и

int nanosleep(const struct timespec *req, struct timespec *rem); ,

объявленная в файле

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

Есть ещё функция clock_nanosleep(), можно включить режимы SHED_FIFO или SCHED_RR для более точной работы, всё это конечно хорошо, но я решил ограничиться только выше указанными.

Таймер должен соответствовать следующим требования: должен минимально потреблять вычислительные ресурсы, должен мгновенно включаться/выключаться, при этом сбрасывать своё состояние и обязательно быть достаточно точным.

Для начала я разберу пару решений которые конечно будут работать, отмечу их плюсы и минусы, но так делать не надо! А в конце покажу решение которое меня в принципе полностью удовлетворяет, реализовано в рамках правил определённого ранее шаблона и надеюсь может быть использовано в дальнейших проектах.

И так первое решение, это для каждого нового экземпляра таймера создавать новый поток, что может кому-то показаться не очень хорошей идеей, но если новые потоки будут почти всегда спать и не тратить вычислительные ресурсы, то наверное в этом нет ничего плохого. А, второе решение, это задействовать всего один дополнительный поток для всех экземпляров таймера.

Для Linux в Си набор функций и типов для работы с потоками определен в файле . При компиляции необходимо добавлять ключ -lpthread. Синхронизация потоков (при доступе к общему ресурсу) может осуществляться мютексами и семафорами. И для практического использования всё выглядит достаточно просто.

Новый класс таймера я решил назвать stimer, соответственно заголовочный файл stimer.h получился следующего вида, и он будет общий для всех решений:

#ifndef _STIMER_H_ #define _STIMER_H_ #include #include "sfuns.h" //В sfuns.h просто определён тип t_bool //Структура определяющая новый класс struct stimer; typedef struct stimer t_stimer; //Структура событий. typedef struct stimer_events < void (*on_time)(t_stimer* timer); void (*on_error)(t_stimer* timer, int* exception); >t_stimer_events; //Конструктор/деструктор t_stimer* stimer_create(void* parent, int* exception); void stimer_destroy(t_stimer* timer); //Интервал void stimer_set_interval(t_stimer* timer, int value); //Активация/деактивация таймера void stimer_set_active(t_stimer* timer, t_bool enable); t_bool stimer_get_active(t_stimer* timer); //Слушатель события void stimer_set_listener(t_stimer* timer, t_stimer_events* listener); //Фамилия родителя void stimer_set_parent(t_stimer* timer, void* parent); void* stimer_get_parent(t_stimer* timer); #endif

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

#include #include #include #include #include #include "sfuns.h" #include "stimer.h" //Объявлена пара экземпляров таймера t_stimer* timer1; t_stimer* timer2; //Слушатель событий от таймера t_stimer_events listener; //Функции обработки событий void ontime_events(t_stimer* timer) < time_t curent_time; time(&curent_time); if(timer == timer1)< printf("Event timer 1 %s",ctime(&curent_time)); >if(timer == timer2) < printf("Event timer 2 %s",ctime(&curent_time)); >> void onerror_events(t_stimer* timer, int* exception) < if(timer == timer1)< printf("Error timer 1 %d\n", *exception); >if(timer == timer2) < printf("Error timer 2 %d\n", *exception); >> int main(int args, char** argv) < unsigned char key; int err; //Присвоение слушателю событий его функций обработчиков listener.on_time = ontime_events; listener.on_error = onerror_events; //Инициализация объектов таймера. Т.к. родитель нам в //данном случае не нужен, то первым параметром пишем NULL timer1 = stimer_create(NULL,&err); if(timer1 == NULL)< printf("Ceate timer1. Error number: %d\n",err); return 0; >timer2 = stimer_create(NULL,&err); if(timer2 == NULL) < printf("Ceate timer2. Error number: %d\n",err); stimer_destroy(timer1); return 0; >//Регистрация слушателя stimer_set_listener(timer1,&listener); stimer_set_listener(timer2,&listener); //Установка периодичности работы таймеров //Если использвать функцию sleep, то интервал будет задаваться в секундах. //Для функции nanosleep, в примерах, интервал будет задаваться в миллисекундах. stimer_set_interval(timer1, 3000); stimer_set_interval(timer2, 5000); stimer_set_active(timer1, true); stimer_set_active(timer2, true); //Обработка клавиатуры do < key = sgetch(); printf("key: %d\n",key); if(key==49)< //1 stimer_set_active(timer1, false); printf("timer1 active false\n"); >if(key==50) < //2 stimer_set_active(timer1, true); printf("timer1 active true\n"); >if(key==51) < //3 stimer_set_active(timer2, false); printf("timer2 active false\n"); >if(key==52) < //4 stimer_set_active(timer2, true); printf("timer2 active true\n"); >> while (key!=27); //ESC stimer_destroy(timer1); stimer_destroy(timer2); return 0; >

Первое решение опробовал на функции sleep(). Максимальная частота обновления данной функции одна секунда, что может быть и достаточным для решения определённого круга задач, но хотелось бы большего, и так см. исходный файл с комментариями:

#include #include #include #include #include "sfuns.h" #include "stimer.h" //Т.к. доступ до функции обработчика событий возможен из нескольких потоков, //то для синхронизации понадобился мютекс static pthread_mutex_t mutex; //Для одноразовой инициализации мьютекса и реализации патерна "одиночка" //просто ввел локальную глобальную переменную static t_bool single_init = false; //Подсчёт количества экземпляров таймера необходим для того, //что бы в деструкторе понимать, что у нас ещё кто-то остался или нет. static int n_timers = 0; //Структура класса с приватными полями struct stimer < int interval; //Периодичность работы t_bool enable; //Состояние активности pthread_t tid; //Идентификатор потока t_stimer_events* listener; //Слушатель событий void* parent; //Чьих рода будет >; //Фукция отдельного потока void* execute_thread(void* arg) < //Передача указателя на экзем созданного объекта таймера t_stimer* timer = (t_stimer*) arg; //Включает возможность немедленного завершения потока pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); do < //Отправляет поток в сон на заданный интервал (в секундах) sleep(timer->interval); //Так как для обработки событий, для всех потоков, используется //одна и таже функция, то доступ к ней синхронизируем мютексом pthread_mutex_lock(&mutex); timer->listener->on_time(timer); pthread_mutex_unlock(&mutex); //Так как поток может завершаться немедленно, //то каких-то условий для выхода из цикла не требуется > while(1); > //Функция конструктор t_stimer* stimer_create(void* parent, int* exception) < int status; //Выделяем память под новый объект t_stimer* new_stimer = malloc(sizeof(t_stimer)); //Проверяем, что всё замечательно или возвращаемся с кодом ошибки if(new_stimer == NULL)< *exception = errno; return NULL; >//Инициализируем переменные структуры-таймера new_stimer->enable = false; new_stimer->interval = 1; new_stimer->parent = parent; n_timers++; //Однократная инициализация общего мютекса if(single_init == false) < status = pthread_mutex_init(&mutex,NULL); if(0!=status)< free(new_stimer); *exception = status; return NULL; >single_init = true; > return new_stimer; > //Функция деструктор void stimer_destroy(t_stimer* timer) < if(n_timers >0)< if(timer->enable == true)< pthread_cancel(timer->tid); timer->enable = false; > free(timer); timer = NULL; n_timers--; if(n_timers == 0) < pthread_mutex_destroy(&mutex); single_init = false; >> > //Включение и выключение таймера void stimer_set_active(t_stimer* timer, t_bool enable) < int status; if((timer->enable == false) && (enable == true))< status = pthread_create(&timer->tid,0,execute_thread,timer); if(0!=status)< timer->listener->on_error(timer,&status); > > if((timer->enable == true) && (enable == false))< status = pthread_cancel(timer->tid); if(0!=status)< timer->listener->on_error(timer,&status); > > timer->enable = enable; > //Во время включения и выключения таймера создаётся и соответственно //уничтожает дополнительный поток. На это тратятся ресурсы, что не соответствует //предъявляемым к таймеру требованиям. Такая реализация имеет место быть //только при условии не частой или единовременной активации таймера. //Далее функции "сеттеры" и "геттеры" для доступа к переменным структуры void stimer_set_interval(t_stimer* timer, int value) < timer->interval =value; > t_bool stimer_get_active(t_stimer* timer) < return timer->enable; > void stimer_set_listener(t_stimer* timer, t_stimer_events* listener) < timer->listener = listener; > void stimer_set_parent(t_stimer* timer, void* parent) < timer->parent = parent; > void* stimer_get_parent(t_stimer* timer) < return timer->parent; >

Получилась откровенная «жесть». Считаю, что «крэшить» потоки и создавать их заново не очень хорошая идея. Решение простое, но на этом все плюсы и заканчиваются поэтому — «в топку».

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

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

Данное решение опробовал с функцией nanosleep(). Исходя из названия, данная функция может обеспечивать задержки в наносекундах, а вот фактическая точность получается в миллисекундах, поэтому задание перемнной value для функции stimer_set_interval() выбрано в миллисекундах.

Реализация имеет следующий вид:

#include #include #include #include #include #include #include "sfuns.h" #include "stimer.h" static t_bool single_init = false; static pthread_t tid = 0; static t_bool terminate = false; static int n_timers = 0; struct stimer < int interval; struct timespec time_before; struct timespec time_after; t_bool enable; t_stimer_events* listener; void* parent; >; static t_stimer** timers; void* execute_thread(void* arg) < int i; int interval; //Период контроля времени задаётся с точностью в 10мс. //Контролировать в данной реализации таймера точность в 1мс не имеет смысла, //так как это почти не возможно и, как правило, не требуется, //а крутить проверку таймеров с такой частотой только "пожерать" ресурсы процессора. struct timespec sleep_period = ; //Период, почти 10 мс do < for(i=0;ienable == false)< //Если таймер не активный, то присваиваем ему начальное значение clock_gettime(CLOCK_REALTIME, &timers[i]->time_before); > > //Засыпаем на 10мс nanosleep(&sleep_period , NULL); for(i=0;ienable == true)< //Получаем текущее значение времени. clock_gettime(CLOCK_REALTIME, &timers[i]->time_after); //Вычисляем прошедшее время ожидания interval = ((timers[i]->time_after.tv_sec-timers[i]->time_before.tv_sec)*1000000000 +timers[i]->time_after.tv_nsec-timers[i]->time_before.tv_nsec)/1000000; //Проверяем условие, если ОК, то обновляем время и формируем событие if(interval >= timers[i]->interval)< clock_gettime(CLOCK_REALTIME, &timers[i]->time_before); timers[i]->listener->on_time(timers[i]); > > > > while (terminate == false); > t_stimer* stimer_create(void* parent, int* exception) < int status; t_stimer* new_stimer = malloc(sizeof(t_stimer)); if(new_stimer == NULL)< *exception = errno; return NULL; >new_stimer->parent = parent; new_stimer->interval = 1000; new_stimer->enable = false; new_stimer->listener = NULL; clock_gettime(CLOCK_REALTIME,&new_stimer->time_before); n_timers++; timers = (t_stimer**)realloc(timers, n_timers*sizeof(t_stimer*)); timers[n_timers-1] = new_stimer; if(single_init == false) < terminate = false; status = pthread_create(&tid,0,execute_thread,NULL); if(0!=status)< *exception = status; n_timers=0; free(timers); free(new_stimer); new_stimer = NULL; return NULL; >single_init = true; > return new_stimer; > void stimer_destroy(t_stimer* timer) < int i; int j; if(n_timers >0) < for(i=0;itimers = (t_stimer**)realloc(timers, (n_timers-1)*sizeof(t_stimer*)); n_timers--; break; > > if(n_timers == 0) < terminate = true; pthread_join(tid,NULL); single_init = false; timers = NULL; >> free(timer); timer = NULL; > void stimer_set_interval(t_stimer* timer, int value) < timer->interval = value; > void stimer_set_active(t_stimer* timer, t_bool enable) < timer->enable = enable; > t_bool stimer_get_active(t_stimer* timer) < return timer->enable; > void stimer_set_listener(t_stimer* timer, t_stimer_events* listener) < timer->listener = listener; > void stimer_set_parent(t_stimer* timer, void* parent) < timer->parent = parent; > void* stimer_get_parent(t_stimer* timer) < return timer->parent; >

Уже лучше, но в данной реализации самый большой минус это постоянная пустая прокрутка потока с большой частотой, а тратить на бездельников, пусть даже в пике, 0.5% от CPU (показания htop) это как-то дороговато. Так, что так делать тоже не годится.

Ализируя полученный результат решил задачу таймера следующим образом:

  1. Для каждого экземпляра таймера создаётся свой индивидуальный поток.
  2. Для задержки потоков на заданный интервал используется функция nanosleep(). Точность интервала в миллисекунду более чем достаточна для решения, я не побоюсь предположить, 90% задач.
  3. Что бы таймер в выключенном состоянии не тратил ресурсы и умел спать неограниченное время, для каждого потока ввёл так называиваемую «переменную условия».
  4. Немедленный сбос таймера и выход из функции nanosleep() может быть реализован через отправку сигнала SIGUSR1 или SIGUSR2 соответствующему потоку.
#include #include #include #include #include #include "sfuns.h" #include "stimer.h" //Мьютекс необходим для синхронизации доступа к общим ресурсам static pthread_mutex_t mutex; //Переменная необходима для однократной инициализации переменных в конструкторе static t_bool single_init = false; static int n_timers = 0; struct stimer < //Свойства таймера struct timespec sleep_period; t_bool enable; //Переменные для работы с потоком pthread_t tid; t_bool terminate; //Те, с кем взаимодействует объект таймера t_stimer_events* listener; void* parent; /* Бездействие таймера в режиме "выключен" будет блокироваться через переменную условия */ pthread_cond_t cond_active; pthread_mutex_t cond_lock; >; void* execute_thread(void* arg) < int status; t_stimer* timer = (t_stimer*) arg; do < //Блокировка потока через переменную условия pthread_mutex_lock(&timer->cond_lock); if(timer->enable == false)< /* Если таймер выключен, то засыпаем и ждём "будильник", сигнал условия деблокировки. */ pthread_cond_wait(&timer->cond_active,&timer->cond_lock); > pthread_mutex_unlock(&timer->cond_lock); /* Если nanosleep нормально доспала, то можно сформировать событие, а для этого необходимо контролировать статус */ status = nanosleep(&timer->sleep_period, NULL); if(timer->enable == true && status == 0)< pthread_mutex_lock(&mutex); timer->listener->on_time(timer); pthread_mutex_unlock(&mutex); > > while(timer->terminate == false); > void signal_handler(int sig) < /* Пустой обработчик сигнала. Фактически данная функция здесь нужна толь для того что бы переопределить реакцию потока на сигнал SIGUSR1. По умолчанию реакция на сигнал это "завершение работы". Сигнал необходим чтобы прервать сон nanosleep.*/ >t_stimer* stimer_create(void* parent, int* exception) < int status; t_stimer* new_stimer = malloc(sizeof(t_stimer)); if(new_stimer == NULL)< *exception = errno; return NULL; >new_stimer->enable = false; new_stimer->terminate = false; new_stimer->sleep_period.tv_sec=1; new_stimer->sleep_period.tv_nsec=0L; new_stimer->parent = parent; if(single_init == false) < //Переопределяем обработчик signal(SIGUSR1,signal_handler); //Инициализируем общий мьютекс status = pthread_mutex_init(&mutex,NULL); if(0!=status) goto crash_object; single_init = true; >status = pthread_mutex_init(&new_stimer->cond_lock,NULL); if(0!=status) goto crash_object; status = pthread_create(&new_stimer->tid,0,execute_thread,new_stimer); if(0!=status)< pthread_mutex_destroy(&new_stimer->cond_lock); goto crash_object; > n_timers++; return new_stimer; crash_object: //Здесь сворачиваемся если что-то пошло не так. if((0 == n_timers) && (single_init == true)) < pthread_mutex_destroy(&mutex); single_init = false; >free(new_stimer); *exception = status; return NULL; > void stimer_set_active(t_stimer* timer, t_bool enable) < timer->enable = enable; //Будим таймер, если он спит pthread_mutex_lock(&timer->cond_lock); pthread_cond_signal(&timer->cond_active); pthread_mutex_unlock(&timer->cond_lock); //Прерываем и сбрасываем nanosleep pthread_kill(timer->tid,SIGUSR1); > void stimer_destroy(t_stimer* timer) < if(n_timers >0)< //Останавливаем таймер timer->terminate = true; stimer_set_active(timer,false); //Ждем завершения потока pthread_join(timer->tid,NULL); //Далее всё чистим pthread_mutex_destroy(&timer->cond_lock); free(timer); timer = NULL; n_timers--; if(n_timers == 0) < pthread_mutex_destroy(&mutex); single_init = false; >> > void stimer_set_interval(t_stimer* timer, int value) < ldiv_t t = ldiv(value, 1000); timer->sleep_period.tv_sec = t.quot; timer->sleep_period.tv_nsec = t.rem * 1000000; > t_bool stimer_get_active(t_stimer* timer) < return timer->enable; > void stimer_set_listener(t_stimer* timer, t_stimer_events* listener) < timer->listener = listener; > void stimer_set_parent(t_stimer* timer, void* parent) < timer->parent = parent; > void* stimer_get_parent(t_stimer* timer) < return timer->parent; >

Может и не много пояснений, но из контекста кода и комментариев всё на первый взгляд должно быть понятно. С таймером надеюсь разобрались.

Файлы с исходным кодом можно скачать на https://github.com/fsdkm/C. Все дальнейшие «поделки» я буду выкладывать в данную папку.

Сеть Ethernet даёт Internet. Для Support Online Connect подавай.

Поэтому далее в соответствии с принятым ранее шаблоном ООП хочу «поюзать» Socket -ы. А начну с клиента TCP.

Использование таймера.

Во многих программах требуется следить за временем или выполнять какие-либо периодические действия. Программы MS-DOS для работы с таймером перехватывали аппаратное прерывание таймера, встраивая свой собственный обработчик для прерывания INT 8h. Обычные приложения Windows не могут самостоятельно обрабатывать прерывания таймера, поэтому для работы с ним нужно использовать другие способы.

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

Так как работа Windows основана на передаче сообщений, логично было бы предположить, что и работа виртуального таймера также основана на передаче сообщений. И в самом деле, приложение может заказать для любого своего окна несколько таймеров, которые будут периодически посылать в функцию окна сообщение с кодом WM_TIMER.

Есть и другой способ, также основанный на передаче сообщений. При использовании этого способа сообщения WM_TIMER посылаются не функции окна, а специальной функции, описанной с ключевым словом _export. Эта функция напоминает функцию окна и, так же как и функция окна, вызывается не из приложения, а из Windows. Функции, которые вызываются из Windows, имеют специальный пролог и эпилог и называются функциями обратного вызова (callback function). Функция окна и функция, специально предназначенная для обработки сообщений таймера, являются примерами функций обратного вызова.

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

DWORD WINAPI GetTimerResolution(void);

Нерегулярность прихода сообщений таймера не вызывает особых проблем, если речь не идет о работе в режиме реального времени. Системы реального времени, основанные на Windows, должны использовать для устройств ввода/вывода, критичных к скорости реакции системы, специальные драйверы. Строго говоря, операционная система Windows не предназначена для работы в режиме реального времени. Windows ориентирована на работу с человеком, когда небольшие задержки событий во времени не имеют никакого значения.

Создание и уничтожение таймера

Для создания виртуального таймера приложение должно использовать функцию SetTimer:

UINT WINAPI SetTimer(HWND hwnd, UINT idTimer,UINT uTimeout, TIMERPROC tmprc);

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

Второй параметр (idTimer) определяет идентификатор таймера (он не должен быть равен нулю). Идентификатор используется только в том случае, если первый параметр функции SetTimer содержит идентификатор окна. Так как для одного окна можно создать несколько таймеров, для того чтобы различать сообщения, приходящие от разных таймеров, приложение при создании должно снабдить каждый таймер собственным идентификатором.

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

Третий параметр (uTimeout) определяет период следования сообщений от таймера в миллисекундах. Учтите, что физический таймер тикает приблизительно 18,21 раза в секунду (точное значение составляет 1000/54,925). Поэтому, даже если вы укажете, что таймер должен тикать каждую миллисекунду, сообщения будут приходить с интервалом не менее 55 миллисекунд.

Последний параметр (tmprc) определяет адрес функции, которая будет получать сообщения WM_TIMER (мы будем называть эту функцию функцией таймера). Этот параметр необходимо обязательно указать, если первый параметр функции SetTimer равен NULL.

Тип TIMERPROC описан в файле windows.h следующим образом:

typedef void (CALLBACK* TIMERPROC)(HWND hwnd, UINT msg, UINT idTimer, DWORD dwTime);

Сравните это с описанием типа WNDPROC, который используется для знакомой вам функции окна:

typedef LRESULT (CALLBACK* WNDPROC) (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

Как видно из описания, функция таймера не возвращает никакого значения, имеет другие (по сравнению с функцией окна) параметры, но описана с тем же ключевым словом CALLBACK:

#define CALLBACK _far _pascal

Возвращаемое функцией SetTimer значение является идентификатором созданного таймера (если в качестве первого параметра функции было указано значение NULL). В любом случае функция SetTimer возвращает нулевое значение, если она не смогла создать таймер. В Windows версии 3.0 максимальное количество созданных во всей системе таймеров было 16. Для Windows версии 3.1 это ограничение снято.

Тем не менее, если приложение больше не нуждается в услугах таймера, оно должно уничтожить таймер, вызвав функцию KillTimer:

BOOL WINAPI KillTimer (HWND hwnd, UINT idTimer);

Первый параметр функции (hwnd) определяет идентификатор окна, указанный при создании таймера функцией SetTimer.

Второй параметр (idTimer) — идентификатор уничтожаемого таймера. Это должен быть либо тот идентификатор, который вы указали при создании таймера (если таймер создавался для окна), либо значение, полученное при создании таймера от функции SetTimer (для таймера, имеющего собственную функцию обработки сообщений).

Функция KillTimer возвращает значение TRUE при успешном уничтожении таймера или FALSE, если она не смогла найти таймер с указанным идентификатором.

Сообщение WM_TIMER

Параметр wParam сообщения WM_TIMER содержит идентификатор таймера, который был указан или получен от функции SetTimer при создании таймера.

С помощью параметра lParam можно определить адрес функции, которая обрабатывает сообщения таймера.

После обработки этого сообщения приложение должно возвратить нулевое значение.

Заметим, что сообщение WM_TIMER является низкоприоритетным. Это означает, что функция DispatchMessage посылает это сообщение приложению только в том случае, если в очереди приложения нет других сообщений. В этом отличие таймера Windows от аналогичных средств MS-DOS, реализованных с помощью перехвата прерывания INT 8h.

Выполнение программы MS-DOS прерывается синхронно с приходом аппаратного прерывания таймера и программа MS-DOS, перехватившая это прерывание, немедленно оповещается о нем. Выполнение приложения Windows тоже, разумеется, прерывается по аппаратному прерыванию таймера, но оповещение об этом событии приходит не всегда, и как правило, позже, вместе с сообщением WM_TIMER.

Первый способ использования таймера

В этом разделе мы рассмотрим первый способ работы с таймером — подключение таймера к окну. В этом случае функция окна, к которому подключен таймер, будет получать сообщения от таймера с кодом WM_TIMER.

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

#define FIRST_TIMER 1 int nTimerID; nTimerID = SetTimer(hwnd, FIRST_TIMER, 1000, NULL);

В данном примере создается таймер с идентификатором FIRST_TIMER, который будет посылать сообщения примерно раз в секунду.

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

KillTimer(hwnd, FIRST_TIMER);

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

KillTimer(hwnd, FIRST_TIMER); nTimerID = SetTimer(hwnd, FIRST_TIMER, 100, NULL);

Второй способ использования таймера

Второй способ работы с таймером заключается в использовании для таймера специальной функции, которая будет получать сообщения WM_TIMER. Эта функция является функцией обратного вызова, определяется с ключевым словом _export и, так же как и функция окна, имеет специальный пролог и эпилог:

void CALLBACK _export TimerProc(HWND hwnd, UINT msg, UINT idTimer, DWORD dwTime);

Как и для функции окна, для функции таймера можно выбрать любое имя. При создании таймера вам надо указать адрес функции таймера, а имя не имеет никакого значения.

Первый параметр функции таймера — идентификатор окна, с которым связан таймер. Если при создании таймера в качестве идентификатора было указано значение NULL, это же значение будет передано функции таймера.

Второй параметр представляет собой идентификатор сообщения WM_TIMER.

Третий параметр является идентификатором таймера, пославшего сообщение WM_TIMER.

И наконец, последний параметр — текущее время по системным часам компьютера. Это время выражается в количестве тиков таймера с момента запуска Windows. Вы можете узнать текущее системное время в любой момент, если воспользуетесь функцией GetCurrentTime или GetTickCount:

DWORD WINAPI GetCurrentTime(void); DWORD WINAPI GetTickCount(void);

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

Если для создания приложения вы пользуетесь современными средствами разработки, такими, как Borland C++ версии 3.1, Microsoft С++ версии 7.0, Microsoft Visual C++, для определения функции обратного вызова достаточно использовать ключевое слово _export. В этом случае вы можете создать таймер, например, так:

nTimerID = SetTimer(hwnd, 0, 1000, (TIMERPROC)TimerProc);

Для удаления таймера в этом случае необходимо использовать идентификатор, возвращенный функцией SetTimer:

KillTimer(hwnd, nTimerID );

Если же используемые вами средства разработки не позволяют указать ключевое слово _export, для подключения функции таймера придется использовать более сложный способ.

Когда вы не можете ограничиться использованием ключевого слова _export, для работы с функциями обратного вызова нужно сделать специальный переходник (thunk), вызвав функцию MakeProcInstance:

FARPROC WINAPI MakeProcInstance(FARPROC lpProc, HINSTANCE hinst);

В качестве первого параметра функции (lpProc) необходимо передать адрес функции, для которой создается переходник, а в качестве второго (hinst) — идентификатор приложения hInstance, полученный функцией WinMain при запуске приложения.

Функция MakeProcInstance для указанной функции создает функцию-переходник, возвращая ее адрес, а также обеспечивает функции доступ к сегменту данных приложения, загружая соответствующим образом сегментные регистры.

Процедура создания таймера с использованием функции MakeProcInstance может выглядеть следующим образом:

TIMERPROC lpfnTimerProc; lpfnTimerProc = (TIMERPROC)MakeProcInstance( (FARPROC)TimerProc, hInstance); nTimerID = SetTimer(hwnd, 0, 1000, (TIMERPROC)lpfnTimerProc);

После уничтожения таймера следует уничтожить созданный функцией MakeProcInstance переходник, для чего следует вызвать функцию FreeProcInstance:

void WINAPI FreeProcInstance(FARPROC lpProc);

Этой функции необходимо передать адрес уничтожаемой функции-переходника:

FreeProcInstance(lpfnTimerProc);

Функции MakeProcInstance и FreeProcInstance можно использовать совместно с современными средствами разработки, понимающими ключевое слово _export. Это не приведет к неправильной работе приложения.

Старые средства разработки приложений Windows требуют, чтобы все функции обратного вызова, такие, как функции окон и функции таймеров, были описаны в файле определения модуля, имеющем расширение .def, при помощи оператора EXPORTS, например:

EXPORTS WndProc TimerProc

Транслятор Borland C++ версии 3.1 распознает ключевое слово _export и автоматически формирует нужный пролог и эпилог для функций обратного вызова. То же самое относится и к трансляторам Microsoft C++ версии 7.0 и Microsoft Visual C++. Поэтому в наших примерах функции MakeProcInstance и FreeProcInstance, а также оператор файла определения модуля EXPORTS не используются.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *