Timers.FLL v1.11 Copyright (C) Jan C. Zawadzki, 1994,1995
Timers is a library of ten functions designed to give Foxpro the ability to
time execution of statements. The library allows the developer to create a
timer - a statement that will be executed every so many (mili) seconds.
The following functions constitute the Timers API (further discussion follows
these "short" explanations):
TimerOn(INT, CHAR, @LOGICAL, INT [,CHAR] )
initiates a Foxpro timer. The first argument is the timer ID, a
unique number that identifies the timer for other API functions.
The ID must be a number between 1 and 3 for the FA release, and 1 to
10 for the registered version. The second parameter is the command
statement that Foxpro will execute when the timer triggers. At
present, this statement must be a single command at most 80
characters long. The third argument is an ADDRESS of a logical
variable that acts as a semaphore. This is the only argument that
MUST be defined before the TimerOn statement. The timer will not
trigger if this variable is FALSE. The fourth argument is the
timer delay MULTIPLIER. All the timers created with the Timers
library are driven off a single Windows timer - its resolution is
the base resolution for all timers created. The delay argument is
multiplied by the base resolution to arrive at the actual delay.
The TimerRes function is used to set the base resolution. The
last optional argument is the command to be executed when the timer
triggers and the semaphore is FALSE.
Returns: Timer ID - no problems
0 - Generic error
(You should definitely NOT get this one)
-1 - Timer ID < 1 or > TIMER_LIMIT
(10 in registered version, 3 in FA)
-2 - Command line too long (> 80 chars)
-3 - Bad semaphore (not a logical?)
-4 - Invalid delay (< 65 msec or > 60,000)
-5 - Windows timer resources exhausted
(quit an app, try again?)
-105 - Alternate command line too long (> 80 chars)
kills the named timer. No frills.
Returns: Timer ID - No problems
0 - Generic error
-1 - Invalid Timer ID passed
(Not an active timer)
Timers uses a single Windows timer regardless of the number of Foxpro
timers serviced. This means that the resolution of these timers is
a constant. This constant can be set with this function. When
called without arguments this function returns the current base
resolution in milliseconds. When an argument is passed, it is used
to set the base resolution in milliseconds. This function only has an
effect BEFORE any timers are activated with TimerOn. The base
resolution can vary between around 60 msec. to 1 minute. The default
setting is 60,000 milliseconds (one minute).
Returns: 0 - Generic error
-1 - Invalid base timer resolution
( < 60 msec or > 60,000 msec)
returns the number of milliseconds since the last Foxpro keypress or
mouse click. This will not catch keystrokes stuffed into the buffer,
and needs Foxpro to check for key and mouse events. The following
will not work:
FOR X = 1 TO 1000
since Foxpro is not even looking at the keyboard. (i.e. you need
a read or something in there...).
returns the number of cycles left on a particular timer. This is in
TimerRes units - if your base resolution is 1000 miliseconds (one
second) and you've defined a timer with TimerOn(1,"foo",@bar,60) the
timer will trigger in 60 seconds; within your regular code you
could check in how many seconds the timer will trigger by calling
pauses and unpauses timer INT. The actual effect of this is closer to
"postpone" - using TimerPause on a timer that is supposed to trigger
in 1 minute will cause it to trigger in 1 minute + the length of time
the timer was paused. If you don't need this behaviour but need to
keep the timer from executing use the timer's semaphore instead.
* Read the following paragraphs carefully. This is VERY important stuff! *
sets/returns the SafeInt setting the library is currently using.
SafeInt is a dangerous feature that lets you force Timers into a mode
which allows ASYNCHRONOUS execution. By default (TRUE) Timers will
wait for Foxpro to idle before triggering a timer. This means that
your code will execute in a pretty stable environment. Using TimerSafe
with a .F. parameter tells Timers to execute at will - whether Foxpro
happens to be executing something or not. This translates to a HIGHLY
unstable environment. This setting for example might allow you to
change SQL query conditions while the query is running (_BAD_ idea).
While all this may (still) sound great, the advantage of this mode
is hardly worth the extra trouble. There is no library that would
allow Foxpro to do two things at once, and Timers is no exception.
While you can do things like checking a query's progress, etc. there
are other ways of doing it (thermometer window) already there, and the
overhead the unsafe mode adds will work to your disadvantage. (The
overhead is there since in unsafe mode the triggers must be checked
continously. In an environment like Windows this is not a good idea.)
This setting can be toggled on and off as Timers is running - the
effect is immediate (asynchronously :).
*** Following is even more important stuff!!! ***
returns a string containing the serial number of the FLL. This
function also returns "* Instance Error *" if another instance of
Timers was detected DURING INITIALIZATION. If there is ANY chance of
the library being used twice at the same computer, MAKE SURE TO CHECK
the return value of this function RIGHT AFTER "set library". Timers
will allow two programs to "share" (different) timers, but the
library will unload only when the *first* instance that used Timers
quits or issues a "set library to ".
*** Following stuff is not important... ***
changes the Windows mouse pointer to the standard arrow.
changes the Windows mouse pointer to the standard hourglass.
returns the instance handle of the calling Foxpro Instance.
* Timers are repetitive - they don't go away until you TimerOff them or
quit Foxpro. (Using just the semaphore to turn a timer off carries a
very slight performance penalty - unless the timer triggers every 500
msec. this should not make a noticeable difference.)
* Timers executes in the "foreground" - Foxpro has no "background" pro-
cessing capabilities. This means that the timer code interrupts Foxpro
command stream and inserts the command you've supplied (more less).
This also means that Timers won't trigger while you are running a long
SQL query for example - Timers cannot interrupt an executing statement.
If a LONG statement is executing when a timer triggers, Timers queues the
timer command to be executed next. This might not happen for minutes in
* TimerRes settings below 60 msec. (varies between systems) are unreliable.
This is a limitation of Windows, and if you truly need higher resolution,
I'd probably recommend using C with some libraries to handle data access.
Without going to VxD's, timing in Windows is a pretty inexact science, so
don't count on anything +/- 60 msec.
* Issuing a QUIT, a CANCEL, or an implicit QUIT (clear all and such) is not
a good idea. It will GPF Foxpro, possibly eating your databases and frying
hardware. Don't do it.
* The Foxpro interpreter used by Timers will not syntax check your code: if
there are errors, the call will die quietly, without _any_ indication of a
problem. All the limitations of the command line interface apply (no if's,
etc.). Generally you should try to test your code by assigning it to a key
perhaps and pressing the key randomly during the main program's execution.
* There is a known problem with the TIME() and WAIT WINDOW functions in Foxpro
Try using WAIT WINDOW " "+TIME() to get around it (it won't work without the
" " in there). (A *LOT* of people do this to test Timers!)
* Certain commands have no effect while a READ is active. By active I mean
any time when the system is waiting for user input. One notable example
is the CLEAR READ command. While this is a rather draconian restriction,
it is fairly easy to code around (set a variable to some magic number,
stuff ESC in the keyboard buffer, when the escape button routine starts
check for the magic value in the variable. If set, do whatever, if not,
proceed with exit.)
* When a timer triggers there is a blink of the mouse pointer. This is caused
by the interpreter automatically switching the cursor to the hourglass shape,
and Foxpro switching it back to a pointer as soon as the timer function
returns. If the timer in question triggers often, or the function it calls
is lengthy, this blink may become rather annoying. Timers provides two
functions that allow the developer to switch the mouse pointer back to the
standard arrow shape (which could be the first line executed in the timer
function). This is not a good practice: the hourglass is an indicator that
tells the user that Foxpro is busy. When Foxpro executes a timer, it is
definitely busy, and all attempts to use the interface will fail while the
timer function is executing. It is perhaps best to either code the timer
function to run fast, or to run it infrequently. If either of the functions
is used, Timers will automatically restore the cursor if the mouse pointer
is moved or the user presses a key - this is to let them know that something
is running after all.
* The timer functions are executed in the TimerID order. If two timers happen
to trigger at the same time, the one with the lower TimerID will execute
first (assuming the semaphore is .T.).
* TimerSafe(.F.) problems: 1) check and recheck the code 2) change code
3) (preferred solution) don't use it.
* Multiple instances are not supported in this version. A temporary
work-around is to copy Timers.FLL to another file (Timers2.FLL or something)
and SET LIBRARY TO TIMERS2.FLL.
* If you get instance error the first time you run the library, you may already
have a TIMERS.DLL loaded on your system. This is apparently true of some
European machines, although I have not been able to trace the symptom to any
particular application or maker. Renaming Timers.FLL to something more
unique ([email protected]
$$&tm.fll or such) seems to fix the problem.
* The T_FoxInst can be used to generate unique ID's if more than one instance
of Foxpro is executing.
* The registered version has 10 available timer slots, all demo versions
A quick and dirty way of verifying that Timers.FLL actually does something is
SET LIBRARY TO TIMERS.FLL
FOO = .T.
from the Command Window. Every minute or so the system time will appear on
the desktop window.
A more complex example:
Assume an application with some sort of a main screen with some data displayed
on it, with buttons and menus accessing other screens. The goal is to
automatically refresh the main screen, and to remind the user to commit the
data to some main database after they have been in the system for two hours.
The first task is straightforward: give a REFRESH.PRG function that handles
the actual updating of screen content, we simply need to call REFRESH on a
* load the library
SET LIBRARY TO TIMERS.FLL
* the refresh semaphore
REFR_SEM = .T.
* initiate the timer
TMP = TimerOn(1,"DO REFRESH",@REFR_SEM,5)
* make sure the return value is OK
IF TMP # 1
The above code will load the library, and initialize a timer. Timer # 1
will trigger every 5 minutes (60,000 msec. is the default base resolution,
which we did not change). To make sure the REFRESH function is not called
while the system is actually doing something, we can do two things:
first, make sure that every code snippet on the main screen starts with
REFR_SEM = .F.
and ends with
REFR_SEM = .T.
which will prevent the library from executing the timer. As a secondary
precaution we could also look at the TimerIdle() function from within the
REFRESH code to make sure the user is not interacting with the application.
The second goal is more complex. Simply setting up a two hour timer will
cause problems if the user happens to be doing something just as the timer
expires - the timer won't trigger (assuming we've set the semaphore!). In
this case the timer going off again in two hours is no good. One way to
handle this situation is to use the alternate command in TimerOn: this is
the command that executes when the timer wakes up and the semaphore is .F.
The function this command might execute can TimerOff the current timer,
ask the user if they want to postpone whatever action the timer originally
was supposed to start, and create a new timer with a smaller delay (2 minutes
or such) to nag them about it as soon (more less) as they finish whatever they
were doing at the moment. To see the effects of the alternate command,
SET LIBRARY TO TIMERS.FLL, FOO = .T., and this time try
? TimerOn(1,'?"Sem .T."+time()',@FOO,1,'?"Sem .F."+time()')
Use of the single and double quotes should be noted. The same result could
be achieved by using the IIF function with another variable.
One last note on an problem that causes some confusion: when two or more
timers are required, all with different delay times, the base resolution
should be set to accommodate the lowest common denominator. If the timers
created need to fire off every 45 seconds for the first timer, and every
2 minutes for the second, the base resolution should be set to 15 seconds,
with the first timer having a delay of 3 (*base resolution), and the
second timer having the delay of 8 (*base resolution). There is no
performance overhead associated with this (none that you could measure with
Tech support is available by email only: from the Internet - [email protected]
from CompuServe: 75462,3230. Updates will be made available on my ftp site
at ftp.csn.org in /yahnz/timers directory. I am always open to suggestions
on how to improve Timers, and (as the beta test folks can attest) I actually
try to accommodate those requests (within reason). Being a registered
user helps! I do not provide free tech support for source code related
questions (one can spend a LOT of time doing that!).
To order Timers please fill out the order form and send it along with a check
or money order for US $65.00 to
Jan C. Zawadzki
PO Box 18235
Denver, CO 80218-0235
The price includes shipping, handling, and kinder, gentler tech support.
Residents of Colorado must add $4.88 tax. You can also purchase Timers via
CompuServe - GO SWREG, and register # 4768. Please read the prompts carefully!
The registered version disk also contain THE SOURCE CODE (Watcom 10.0), and
some miscellaneous Foxpro source code for things like reading/writing private
.ini files, etc.
New versions of Timers can be found on the Internet at ftp.csn.org in
/yahnz/fox, on CompuServe (GO FOX, library #4 - WinAPI/FLL), and on the
Programmer's Corner, one of the best BBS's around (410-995-6877). If you
have any questions please email me ([email protected]
or 75462,3230 on CompuServe).
Many thanks to the beta folks, Paul & Steve @ Watcom, and a number of people
at Microsoft tech support...!
By purchasing Timers you are buying a license to use the binary or source code
versions of the library. I, Jan C. Zawadzki, retain ownership of the Timers
library, including any copies.
You may distribute the registered version of the Timers.FLL file with any .APP
or .EXE file that you build using the Timers library. The source code may not
be distributed in any form without my prior permission in writing.
The copyright notice must not be removed from the source code. The demo version
of the library is intended to be just that - a demo. There are two differences
between the demo version and the registered version: the demo is limited to
three timers, and all demos will display a copyright message when the library is
No version of the Timers library may be distributed as a programming library or
a part of a programming library without my prior written permission.
I make no representation or warranty with respect to the adequacy of this
documentation or the program which it describes for any particular purpose
or with respect to its adequacy to produce any particular result. In no
event will I be liable for special, direct, indirect, or consequential
damages, losses, costs, charges, claims, demands, or claim for lost
profits, fees or expenses of any nature or kind.
Your use of the Timers.FLL library constitutes an agreement to the above.