April 26, 2024, 10:52:34 PM

News:

Own IWBasic 2.x ? -----> Get your free upgrade to 3.x now.........


How to implement timers in console mode

Started by Locodarwin, April 15, 2008, 02:43:55 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Locodarwin

I engineer kiosk-based systems where I work.  I'm constantly required to create console mode "watcher" type applications that run in the background and perform specific tasks at assigned intervals.  In C/C++, or in a window-based application, I can do this easily with calls to the WinAPI SetTimer() and KillTimer() functions.

EBasic has a similar set of wrapper commands, called STARTTIMER and STOPTIMER.  There's just one problem.

When working on console mode programs, I realized I couldn't use EBasic's STARTTIMER and STOPTIMER commands because they operate under the assumption the programmer is creating those timers for a windows (non-console) application.  The very first parameter for STARTTIMER expects a WINDOW struct, which console mode applications do not use.  One could create an empty WINDOW struct and pass the win.hwnd parameter as NULL, I presume, but that seems inefficient and "hackish."

There is still another problem.  STARTTIMER (like SetTimer) needs a message pump running in order to dispatch whatever timers have been implemented.  Console applications don't often use those, as there aren't many Windows messages ever coming into our application that we care about.

MS officially states that the WinAPI SetTimer function (which is what EBasic uses) cannot be used in console applications unless run in a separate thread.  Having a bit of C/C++ experience under my belt, I know this isn't true.  So I've leveraged a bit of C/C++ programming in order to implement timers in EBasic.

As long as you pass NULLs as window handles, and write your own console-style message pump, you can implement timers in a single thread for a console application in EBasic.

Compile & run this as a console app:


' Console Timer Example
' By SEO

' ------------------- Beginning of snip you can remove if including windows.inc
Type MSG
DEF hwnd AS INT
DEF message AS INT
DEF wParam AS INT
DEF lParam AS INT
DEF time AS INT
DEF pt as POINT
EndType

DECLARE IMPORT, _SetTimer ALIAS SetTimer(hWnd as INT, nIDEvent as INT, uElapse as INT, lpTimerFunc as INT),INT
DECLARE IMPORT, _KillTimer ALIAS KillTimer(hwnd as INT, nIDEvent as INT), INT
DECLARE IMPORT, _Sleep ALIAS Sleep(dwMilliseconds as INT)
DECLARE IMPORT, _GetMessage ALIAS GetMessageA(lpMsg as MSG, hwnd as INT, wMsgFilterMin as INT, wMsgFilterMax as INT),INT
DECLARE IMPORT, _DispatchMessage ALIAS DispatchMessageA(lpMsg AS MSG),INT
' ------------------- End of snip you can remove if including windows.inc

MSG msg

Print "Timer example."
Print "This console window should print messages every 1.5 and 4 seconds."

TimerOn(1, 4000, &TimerCallback1) ' Timer 1 will fire every 4 seconds
TimerOn(2, 1500, &TimerCallback2) ' Timer 2 will fire every 1.5 seconds

' Message pump (console style!)
While _GetMessage(msg, NULL, 0, 0)

_Sleep(10) ' Or do whatever your program needs to do during the pump loop

' The message pump needs a _DispatchMessage() call in order to process the callbacks
_DispatchMessage(msg)
Wend

' The TimerOn function
Sub TimerOn(id as UINT, time as UINT, Callback as UINT), INT
_SetTimer(NULL, id, time, Callback)
EndSub

' The TimerOff function (call this with the timer id when the timer is no longer needed or wanted)
Sub TimerOff(id as UINT)
_KillTimer(NULL, id)
Return
EndSub

' First timer's callback function
Sub TimerCallback1()
Print "Timer1 fires!"
EndSub

' Second timer's callback function
Sub TimerCallback2()
Print "Timer2 fires!"
EndSub


In this example I implemented callback subroutines instead of in-pump handling.  Callbacks make the most sense to me as I try to keep any message pump I write clear of too much code, for readability's sake.  One could alter the above code to use an If(msg.message = WM_TIMER) type of test inside the message pump, and therefore allow handling code inside the pump.  If anyone is interested, I could provide an example.

Admittedly, the code above is a workaround and almost makes one wonder why to do it at all in EBasic.  However, as I go along I find lots of interesting ways to get EBasic to do what I want, and as I collect code, I begin to see how much easier it is to write fluid applications - and usually in half the time.

I hope someone finds this useful.  :)

-S


Haim

Thanks for sharing this.
It will be very handy indeed.

Haim

Egil

Thanks for sharing!
I do the same thing here in console mode, with less keyboard tapping, using the MILLISECS() function. Maybe not to the same resolution though, but it works, and is sure more easy to understand for newbies like myself. ::)
Support Amateur Radio  -  Have a ham  for dinner!