Как правильно работать с мультимедийным таймером?

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Как правильно работать с мультимедийным таймером?

Сообщение Andreich » 14.02.2012 16:01:01

Всем доброго времени суток! Перерыл весь Интернет, но проблему так и не решил... каким образом следует реализовать мультимедийный таймер в среде Lazarus? В Delphi это делается так:

Код: Выделить всё
var
  Form1: TForm1;
  mmResult : integer;

...

procedure TimeCallBack(uTimerID, uMsg: UINT; dwUser, dw1, dw2: DWORD);stdcall;
var px1, py1, px2, py2 : integer;
begin
  px1:=random(100);
  py1:=random(100);
  px2:=random(100);
  py2:=random(100);
  Form1.Image1.Canvas.Brush.Color:=RGB(random(255),random(255),random(255));
  Form1.Image1.Canvas.Rectangle(px1,py1,px2,py2);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  mmResult := TimeSetEvent(10,0, @TimeCallBack, 0, TIME_PERIODIC);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Form1.Image1.Canvas.Rectangle(0,0,100,100);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  TimeKillEvent(mmResult);
end;

При переносе этого кода на Lazarus внешне все проходит без ошибок, но при запуске на исполнение программа самопроизвольно вырубается спустя несколько секунд (время может быть разным от 1 до 50 сек.). Проверил на Delphi - все работает идеально. В чем загвоздка?

Lazarus 0.9.30 & FPC 2.4.2
Andreich
постоялец
 
Сообщения: 268
Зарегистрирован: 17.04.2008 12:33:43

Re: Как правильно работать с мультимедийным таймером?

Сообщение Bupyc » 15.02.2012 12:29:26

А Вы уверены, что у Вас выполнение процедуры TimeCallBack происходит в контексте основного потока приложения и там вот прям так безнаказанно можно вызывать LCL код? Я бы все таки через некий аналог Synchronize из TThread вызывал тот код, который Вы сейчас напрямую вызываете.

Вызовите API GetCurrentThreadId при запуске события (на кнопке) и в процедуре TimeCallBack. Если возвращаются разные значения, то контекст потоков разный и принципиально неверно вызывать код из библиотеки LCL в обработчике таймера, причем как в Delphi, так и в Lazarus.
Bupyc
постоялец
 
Сообщения: 137
Зарегистрирован: 29.08.2007 18:22:42

Re: Как правильно работать с мультимедийным таймером?

Сообщение Andreich » 15.02.2012 16:00:51

Bupyc писал(а):Вызовите API GetCurrentThreadId при запуске события (на кнопке) и в процедуре TimeCallBack. Если возвращаются разные значения, то контекст потоков разный и принципиально неверно вызывать код из библиотеки LCL в обработчике таймера, причем как в Delphi, так и в Lazarus.

Значения разные. Исключил из TimeCallBack обращение к объектам LCL для проверки заменив содержимое процедуры абстрактными вычислениями,.. все работает без сбоев! Спасибо за данную подсказку, просто я раньше практически не работал с потоками.

Теперь осталось разобраться как производить отрисовку графики при срабатывании таймера (собственно для того и нужен быстрый мультимедийный таймер).
Andreich
постоялец
 
Сообщения: 268
Зарегистрирован: 17.04.2008 12:33:43

Re: Как правильно работать с мультимедийным таймером?

Сообщение Bupyc » 15.02.2012 16:31:05

Если значения аргументов процедуры TimeCallBack не используются, можно через функцию PostMessage отправлять форме сообщение, о том что сработал таймер. Пусть она по этому сообщению что-то делает. Если аргументы TimeCallBack (uTimerID, uMsg, dwUser, dw1, dw2) нужны, то можно при каждом срабатывании таймера динамически выделять в куче память под структуру и помещать значения аргументов в неё, а затем одним из аргументов сообщения (wParam или lParam) передавать полученный указатель в форму. В обработчике сообщения не забываем освобождать выделенную память.

Добавлено спустя 59 секунд:
Andreich писал(а): Спасибо за данную подсказку, просто я раньше практически не работал с потоками.


Я работаю уже лет 15 :) Всякое видел.

Добавлено спустя 33 минуты 27 секунд:
Кстати, вот еще о чем подумал. Если будете передавать аргументы процедуры TimeCallBack в основной поток приложения тем способом, который я Вам предложил, выделение памяти в куче делайте с помощью виндовых функций (например, HeapAllocate).
Поясню, почему так: не знаю как в Лазарусе, в дельфях менеджер памяти анализирует значение глобальной переменной IsMultithread при работе с динамической памятью.

Как говорит help
IsMultiThread is set to true by BeginThread and class factories


Таким образом, если Вы не создадите никаких потоков с помощью BeginThread или наследника от класса TThread, то переменная IsMultiThread останется равной False. Хотя де-факто приложение будет мультипоточным. Если при этом вызывать в TimeCallBack код, который будет обращаться к менеджеру памяти (функции GetMem, FreeMem, Move, FillChar, работа со строками, динамическими массивами и т.д.), то поведение программы может стать непредсказуемым.

В общем, совет - наполнение TimeCallBack сделайте минимальным. Иначе можно наглядеться магических глюков в произвольном месте программы.
Bupyc
постоялец
 
Сообщения: 137
Зарегистрирован: 29.08.2007 18:22:42

Re: Как правильно работать с мультимедийным таймером?

Сообщение zub » 15.02.2012 17:40:22

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

по тойже причине что и вызывать LCLный код не стоит. Лучше заполнять заранее выделенный (или объявленный) в основной программе кусок памяти
Самое простое - внутри TimeCallBack просто устанавливать флажок, по установке флажка отрисовывать в onIdle и снова его сбрасывать
zub
долгожитель
 
Сообщения: 2887
Зарегистрирован: 14.11.2005 23:51:26

Re: Как правильно работать с мультимедийным таймером?

Сообщение Bupyc » 15.02.2012 18:21:10

zub писал(а):Выделять память в TimeCallBack, освобождать внутри обработчиков - верный путь к
Иначе можно наглядеться магических глюков в произвольном месте программы.


Утечку памяти можно получить, особенно при закрытии программы. Насчет глюков - не уверен. Довольно часто так делаю. Причем в коде, работающем при высокой нагрузке по нескольку месяцев. Проблем не наблюдал. Может везло? :)

zub писал(а):Самое простое - внутри TimeCallBack просто устанавливать флажок, по установке флажка отрисовывать в onIdle и снова его сбрасывать


Может получиться так, что TimeCallBack вызовется 10 раз, а OnIdle - 1. Если такой нюанс не критичен, то этот способ подходит.
Bupyc
постоялец
 
Сообщения: 137
Зарегистрирован: 29.08.2007 18:22:42

Re: Как правильно работать с мультимедийным таймером?

Сообщение zub » 15.02.2012 18:31:20

>>Может получиться так, что TimeCallBack вызовется 10 раз, а OnIdle - 1. Если такой нюанс не критичен, то этот способ подходит.
в лцлприложении по любому так и получится, вернее нет никаких гарантий что так не получится, как не ухитряйся возвращать данные\вызывать методы из мультимедийного таймера. ИМХО.

>>Может везло?
Может, мне один раз (давным давно, вроде еще в Делфи) не повезло, с тех пор ни-ни :lol: Возможно сейчас такие выделения не страшны
zub
долгожитель
 
Сообщения: 2887
Зарегистрирован: 14.11.2005 23:51:26

Re: Как правильно работать с мультимедийным таймером?

Сообщение B4rr4cuda » 15.02.2012 20:22:26

Bupyc писал(а):Если значения аргументов процедуры TimeCallBack не используются, можно через функцию PostMessage отправлять форме сообщение, о том что сработал таймер. Пусть она по этому сообщению что-то делает. Если аргументы TimeCallBack (uTimerID, uMsg, dwUser, dw1, dw2) нужны, то можно при каждом срабатывании таймера динамически выделять в куче память под структуру и помещать значения аргументов в неё, а затем одним из аргументов сообщения (wParam или lParam) передавать полученный указатель в форму. В обработчике сообщения не забываем освобождать выделенную памят

По рукам бить надо за такие советы написания кода на лазарусе и фрипаскале =))
В таком случае лучше уж кроссплатформенно делать передачу событий, через IPC (модуль simpleipc, классы TSimpleIPCServer и TSimpleIPCClient).
Аватара пользователя
B4rr4cuda
энтузиаст
 
Сообщения: 693
Зарегистрирован: 28.12.2007 07:48:35

Re: Как правильно работать с мультимедийным таймером?

Сообщение Bupyc » 15.02.2012 22:05:59

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

Так что совет вполне нормальный. Хотя, согласен, далеко не самое красивое решение. Можно сделать лучше. Особенно если предполагается, что код будет работать где то еще кроме Windows. Но в этом случае и мультимедийными таймерами лучше не пользоваться.
Bupyc
постоялец
 
Сообщения: 137
Зарегистрирован: 29.08.2007 18:22:42

Re: Как правильно работать с мультимедийным таймером?

Сообщение Brainenjii » 16.02.2012 00:16:54

Есть ещё такая штука - http://wiki.lazarus.freepascal.org/EpikTimer
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: Как правильно работать с мультимедийным таймером?

Сообщение Andreich » 16.02.2012 00:36:03

Brainenjii писал(а):Есть ещё такая штука - http://wiki.lazarus.freepascal.org/EpikTimer

Да, я ставил этот компонент, но так толком и не разобрался, как привязать к срабатыванию какое-то действие... там нет события onTimer. В демонстрационном примере акцент сделан на точное измерение временных интервалов, а не на обработку события по таймеру.
Andreich
постоялец
 
Сообщения: 268
Зарегистрирован: 17.04.2008 12:33:43

Re: Как правильно работать с мультимедийным таймером?

Сообщение alexey38 » 16.02.2012 06:07:52

Andreich писал(а):
Brainenjii писал(а):Есть ещё такая штука - http://wiki.lazarus.freepascal.org/EpikTimer

Да, я ставил этот компонент, но так толком и не разобрался, как привязать к срабатыванию какое-то действие... там нет события onTimer. В демонстрационном примере акцент сделан на точное измерение временных интервалов, а не на обработку события по таймеру.


Если просто нужно замерять временные интервалы под виндой, то используйте: GetTickCount - выдает в мсек. А лучше GetSystemTimeAsFileTime - там вообще с более чем 1 мксек точностю идет замер и нет переполнения, как в GetTickCount.
alexey38
долгожитель
 
Сообщения: 1627
Зарегистрирован: 27.04.2011 19:42:31

Re: Как правильно работать с мультимедийным таймером?

Сообщение Andreich » 16.02.2012 10:02:52

alexey38 писал(а):Если просто нужно замерять временные интервалы под виндой, то используйте: GetTickCount - выдает в мсек. А лучше GetSystemTimeAsFileTime - там вообще с более чем 1 мксек точностю идет замер и нет переполнения, как в GetTickCount.

В том то все и дело, что мне не нужно замерять интервалы, а нужно выполнять некоторый набор действий по срабатыванию таймера.
Andreich
постоялец
 
Сообщения: 268
Зарегистрирован: 17.04.2008 12:33:43

Re: Как правильно работать с мультимедийным таймером?

Сообщение alexey38 » 16.02.2012 10:16:43

Andreich писал(а):
alexey38 писал(а):Если просто нужно замерять временные интервалы под виндой, то используйте: GetTickCount - выдает в мсек. А лучше GetSystemTimeAsFileTime - там вообще с более чем 1 мксек точностю идет замер и нет переполнения, как в GetTickCount.

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


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

Так что м.б. нужен системный таймер, а не мультимедийный. Он собственно мультимедийный, чтобы работать с какими-то устройствами, а не для вывода на формы. А если нужен мультимедийный, то можно не call-back использовать, а устанавливать Event. Так как работа с многопоточными приложениями требует использование всяких синхронизаций, типа критических секций и т.п., иначе будут разные глюки.
alexey38
долгожитель
 
Сообщения: 1627
Зарегистрирован: 27.04.2011 19:42:31

Re: Как правильно работать с мультимедийным таймером?

Сообщение B4rr4cuda » 16.02.2012 21:36:57

Bupyc вопрос не в мультимедийном таймере, а в методике "общения" с потоками) А именно в массовом использовании PostMessage и SendMessage с AllocateWnd для создания моста между потоками. Это весьма удобно и хорошо использовать в делфи, но на лазаре - это проблема именно ввиду его кроссплатформенности. Данная методика очень "привязывается" и используется везде, где надо и не надо, что потом создает серьезные проблемы. Столкнулся лично, поэтому вопрос "больной".. :) Прошу прощения, если задел чувства или оскорбил.
Аватара пользователя
B4rr4cuda
энтузиаст
 
Сообщения: 693
Зарегистрирован: 28.12.2007 07:48:35

След.

Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 203

Рейтинг@Mail.ru