Older Version
Newer Version
nukesrus21
May 23, 2011
Multiple-Timer Routines
May23, 20011By Brandon Parker
Native Liberty Basic Timer Command
The native Liberty Basic Timer command manages a single Windows timer. While this is useful for controlling program flow at certain intervals there are a few drawbacks.- There is ONLY ONE Timer.
- There are also two bugs associated with the Timer command that cause issues found here: Liberty Basic Bug Tracker - Events
- Timer events will built up if the program is busy doing something else or if a Notice Dialog is issued and is not acnkowledged prior to the Timer firing.
Here is the example from the Liberty Basic Helpfile:
'set a timer to fire in 3 seconds
'using branch label event handler
timer 3000, [itHappened]
'wait here
wait
[itHappened]
'deactivate the timer
timer 0
confirm "It happened! Do it again?"; answer
if answer then
'reactivate the timer
timer 3000, [itHappened]
wait
end if
end
As you can see the code to utilize the native Timer command is very simple and easy to use. Were it not for the drawbacks mentioned previously we would not require anything further. That leads me to the topic of this article.
Timer Objectives
We should start by stating the objectives for our timer.- The user should be able to create multiple timers.
- The timer should work as well or better than the native Liberty Basic Timer.
- The timer should be easy to use and have an adjustable interval.
- The timer should have the capability of being removed during program execution.
- The timer events should not build up, but should cause the program to branch off and return when completed.
- The timer should be destroyed when the program execution has completed.
This is not exactly the simplest to do, but it can be acoomplished as we will soon see.
Introducing the Timer Queue Functions. These functions are Kernel32.dll which is part of the Windows API. These functions make it possible for a Liberty Basic program to take advantage of Windows' Thread Pooling capabilities where we can basically allow "worker threads" that are managed by Windows to control our timers and the CallBacks to functions within the program. Not only does this approach make it fairly simple to use multiple timers it also prevents our Liberty Basic program from ever just sitting idle while we peruse menus, context menus, or drag the window around.
Now that we have a general idea of what will be happening we should get right down to buisiness and start learning how to use a few wrapper functions for these API calls.
In order to use the functions that will follow we will need to set up a couple structures and some constants that we will be using along with a few other values that will be used for the function calls. They are as follows:
'The first Structure is for holding the handle
'to the created Timer Queue
Struct TimerQueue, handle As ulong
'The Second Structure is required for returning the
'handle of the timer
Struct phNewTimer, handle As ulong
'Now we will define a few flags that can be used.
WT.EXECUTEDEFAULT = _NULL 'By default, the callback function is queued to a non-I/O worker thread.
WT.EXECUTEONLYONCE = 8 'The timer will be set to the signaled state only once.
'If this flag is set, the Period parameter must be zero.
DueTime = 500
Period = 1500
Flags = WT.EXECUTEDEFAULT OR WT.EXECUTELONGFUNCTION
Now that we have that all set. We should probably create a windows with a few things in it to play with. Note that all of Dynamic Array Functions have been included to simplify the expandability of the functions.
NoMainWin
Global True : True = 1
Global False : False = 0
WindowWidth = 270
WindowHeight = 150
StaticText #Test.timer1, "", 90, 25, 100, 25
StaticText #Test.timer2, "", 90, 55, 100, 25
StaticText #Test.timer3, "", 90, 85, 100, 25
Open "Multiple Timers via API" For Window As #Test
Print #Test, "TrapClose Quit"
Ok; so we have our window all set up. Should we get started on creating timers? Well, not just yet. We should go right ahead and set up the CallBacks and associated Functions that our timers will be utilizing that way we are all set to go once we create the timers.
CallBacks and Associated Functions
At this point if you are not familiar with CallBacks it would probably be a good idea to take a step back and study them prior to returning to this tutorial. For the sake of simplicity and to shorten this I will just provide the CallBacks and associated Functions rather than discussing the how/ why of them. Although I would like to mention that these CallBacks require only two arguments; sending them more will cause failure and their return types are Void although this is not necessarily set in stone.The CallBacks should be placed at the beginning of the program prior to attempting to create any Timer Queue Timers.
CallBack WaitorTimerCallBackAdd, WaitorTimerCallBack(ULong,ULong),Void
CallBack WaitorTimerCallBack2Add, WaitorTimerCallBack2(ULong,ULong),Void
CallBack WaitorTimerCallBack3Add, WaitorTimerCallBack3(ULong,ULong),Void
Function WaitorTimerCallBack(ULong,ULong)
Print #Test.timer3, ""
Print #Test.timer1, "Timer #1 Fired"
End Function
Function WaitorTimerCallBack2(ULong,ULong)
Print #Test.timer1, ""
Print #Test.timer2, "Timer #2 Fired"
End Function
Function WaitorTimerCallBack3(ULong,ULong)
Print #Test.timer2, ""
Print #Test.timer3, "Timer #3 Fired"
End Function
Creating a Timer Queue
We will start by creating a timer queue for our program where all of our timers will reside. Although this is not necessary if you will only be creating a few timers it never hurts for a program to have its own timer queue. In order to do this we will need to call the CreateTimerQueue Function from the Kernel32.dll.Here is the wrapper function we will be using:
Function CreateTimerQueue()
CallDLL #kernel32, "CreateTimerQueue", CreateTimerQueue As ulong
End Function
Notice that the CreateTimerQueue() Function we have created requires no parameters, but does return a value; the handle to the Timer Queue. This value we will need to keep so we will store it in our TimerQueue Structure. We will call the CreateTimerQueue() Function in the following manner which will allow us to store the beforementioned handle to the Timer Queue.
TimerQueue.handle.struct = CreateTimerQueue()
And that is all you have to do to create a Timer Queue for a Liberty Basic program. With that being said before we jump into creating Timer Queue Timers we first should look at how to destroy the Timer Queue and any Timer Queue Timers because if we attempt to close our program without doing so we will have a huge mess on our hands as the thread(s) in the thread pool that are controlling our Timer(s) will not know that the program has been closed and will attempt to activate the code for the Timer(s). This normally will cause a few pop-ups from Windows and will cause the program to crash (even the Liberty Basic IDE) all the way to the Desktop.
Deleting a Timer Queue and all Timer Queue Timers
We will do destroy the Timer Queue and all Timer Queue Timers by calling the DeleteTimerQueueEx Function from the Kernel32.dll.Here is the wrapper function we will be using:
Function DeleteTimerQueueEx(TimerQueue, CompletionEvent)The DeleteTimerQueueEx() Function takes two arguments as input; TimerQueue and CompletionEvent. TimerQueue is the handle to the Timer Queue. Remember from earlier that we stored that handle in TimerQueue.handle.struct when we called the CreateTimerQueue() Function. CompletionEvent is basically what the name describes; a handle to a completion event that will be called when all Timer Event have completed. We will be passing _INVALID_HANDLE_VALUE as the argument for CompletionEvent as this causes the function to wait for all callback functions to complete before returning. The function returns a non-zero value if successful and zero otherwise. This will ensure that all of our expected timed events are taken care of and makes for a very clean closing process for the Timer Queue altogether.
CallDLL #kernel32, "DeleteTimerQueueEx", TimerQueue As ulong, _
ComletionEvent As ulong, _
DeleteTimerQueueEx As ulong
result$ = ReDimOrSetStringArray$("TimerQueueTimer", "NULL*", 0)
End Function
To illustrate how this would be performed when closing a window here is a subroutine that would be used to close our program. Notice that we make a call to the DeleteTimerQueueEx() Function prior to closing our window.
Sub Quit handle$
result = DeleteTimerQueueEx(TimerQueue.handle.struct, _INVALID_HANDLE_VALUE)
Close #handle$
End
End Sub
Creating a Timer Queue Timer
Now we can get to the good part.... Creating Timers!!
We will accomplish this by calling the CreateTimerQueueTimer Function from the Kernel32.dll.
As we examine the wrapper function CreateTimerQueueTimer() we will notice that the function expects six arguments.
- TimerName$ - This can be any name you wish to give the timer and is used with the Dynamic Array Functions.
- TimerQueue - This is the handle to the Timer Queue that we created by calling the CreateTimerQueue() Function.
- WaitorTimerCallBackAdd - This is the address of the CallBack Function we want the Timer to call.
- DueTime - This is the amount of time after the Timer is created that the process waits prior to calling the supplied Function in milliseconds.
- Period - This is the Timer Interval.
- Flags - This OR'd value is a combination of flags that tells Windows how the Timer should work. (You will find a list of some, but not all possible values in the full example below.)
The function returns a non-zero value if successful and zero otherwise. The function will also return the handle to the newly created Timer in phNewTimer.handle.struct if it is successful.
You can also see that the Dynamic Array Function within the CreatetimerQueueTimer() Function will be storing the Name, Handle, and Period of any Timer that we create inside the TimerQueueTimer$() Array.
Function CreateTimerQueueTimer(TimerName$, TimerQueue, WaitorTimerCallBackAdd, DueTime, Period, Flags)
CallDLL #kernel32, "CreateTimerQueueTimer", phNewTimer As struct, _
TimerQueue As ulong, _
WaitorTimerCallBackAdd As ulong, _
parameter As long, _
DueTime As ulong, _
Period As ulong, _
Flags As ulong, _
CreateTimerQueueTimer As long
TimerQueueTimer$(NextAvailableElementStringArray("TimerQueueTimer", False)) = TimerName$ + " " + _
str$(phNewTimer.handle.struct) + " " + _
str$(Period)
End Function
Ok, now that we have the function we should create a few timers to get things off to a good start. We will create three timers for the three CallBacks and associated Functions that we set up in the beginning of this tutorial.
result = CreateTimerQueueTimer("Timer1", TimerQueue.handle.struct, WaitorTimerCallBackAdd, DueTime, Period, Flags)
result = CreateTimerQueueTimer("Timer2", TimerQueue.handle.struct, WaitorTimerCallBack2Add, DueTime + Int(Period * .3), Period, Flags)
result = CreateTimerQueueTimer("Timer3", TimerQueue.handle.struct, WaitorTimerCallBack3Add, DueTime + Int(Period * .6), Period, Flags)
Now that we have created a few timers we should see how to change a Timer. That can be accomplished by calling the ChangeTimerQueueTimer Function from the Kernel32.dll.
Changing a Timer Queue Timer's Properties
Here we have the wrapper function:Warning: This function should not be used and should be replaced by the ChangeTimerQueueTimerbyName() Function if the Dynamic Array Functions are in use as it will not update the TimerQueueTimer$() Array.
Function ChangeTimerQueueTimer(TimerQueue, hTimer, DueTime, Period)Examining the ChangeTimerQueueTimer() Function we find that it requires four inputs:
If hTimer = Val(Word$(TimerQueueTimer$(i), 2)) Then
CallDLL #kernel32, "ChangeTimerQueueTimer", TimerQueue As ulong, _
hTimer As ulong, _
DueTime As ulong, _
Period As ulong, _
ChangeTimerQueueTimer As ulong
End Function
- TimerQueue - This is the handle to the Timer Queue that we created by calling the CreateTimerQueue() Function.
- hTimer - This is the handle to the Timer that we want to change.
- DueTime - This is the amount of time after the Timer is changed that the process waits prior to calling the supplied Function in milliseconds.
- Period - This is the NEW Timer Interval.
The function returns a non-zero value if successful and zero otherwise.
This is an example of how to call the function:
result = ChangeTimerQueueTimer(TimerQueue.handle.struct, hTimer, DueTime, Period)Now keeping track of all of those Timer handles would be a pain had we not set everything up to where the Dynamic Array Functions would storing our Timer information inside the TimerQueueTimer$() Array. This makes it possible to create another function that calls the ChangeTimerQueueTimer() Function where we supply the Name of the Timer instead of the handle.
Here is that function; ChangeTimerQueueTimerbyName():
Function ChangeTimerQueueTimerbyName(TimerName$, TimerQueue, DueTime, Period)
For i = 0 To UBoundStringArray("TimerQueueTimer")
If (TimerQueueTimer$(i) = "") Then Exit For
If TimerName$ = Word$(TimerQueueTimer$(i), 1) Then
TimerQueueTimer$(i) = Word$(TimerQueueTimer$(i), 1) + " " + _
Word$(TimerQueueTimer$(i), 2) + " " + _
str$(Period)
CallDLL #kernel32, "ChangeTimerQueueTimer", TimerQueue As ulong, _
hTimer As ulong, _
DueTime As ulong, _
Period As ulong, _
ChangeTimerQueueTimer As ulong
Exit For
End If
Next i
End Function
This is how you would change a Timer by Name:
result = ChangeTimerQueueTimerbyName("Timer1", TimerQueue.handle.struct, DueTime, Period)
Deleting Timer Queue Timers
With respect to deleting timers there have been two functions created pretty much the same as with changing the timers. The first wrapper function will delete a Timer based on its handle and the second function will delete a Timer based on the name and is dependent upon the first function, but provides extra flexibility.The first is the DeleteTimerQueueTimer() Function:
Function DeleteTimerQueueTimer(TimerQueue, hTimer, CompletionEvent)The second is the DeleteTimerQueueTimerbyName() Function:
CallDLL #kernel32, "CreateTimerQueueTimer", TimerQueue As ulong, _
hTimer As struct, _
ComletionEvent As ulong, _
DeleteTimerQueueTimer As long
End Function
'CompletionEvent can be _NULL or _INVALID_HANDLE_VALUE (recommend the latter)Both functions should be called as follows:
Function DeleteTimerQueueTimerbyName(timerName$, TimerQueue, CompletionEvent)
For i = 0 To UBoundStringArray("TimerQueueTimer")
If (TimerQueueTimer$(i) = "") Then Exit For
If Word$(TimerQueueTimer$(i), 1) = timerName$ Then
DeleteTimerQueueTimerbyName = DeleteTimerQueueTimer(TimerQueue, _
Val(Word$(TimerQueueTimer$(i), 2)), _
CompletionEvent)
TimerQueueTimer$(i) = ""
result = CompactStringArray("TimerQueueTimer")
Exit For
End If
Next i
End Function
1. DeleteTimerQueueTimer() Function
result = DeleteTimerQueueTimer(TimerQueue.handle.struct, hTimer, CompletionEvent)
2. DeleteTimerQueueTimerbyName() Function
result = DeleteTimerQueueTimerbyName(time"Timer1", TimerQueue, CompletionEvent)With either one of these calls we will be passing _INVALID_HANDLE_VALUE as the argument for CompletionEvent as this causes the function to wait for all callback functions to complete before returning. These functions return a non-zero value if successful and zero otherwise.
The last function to discuss is the TimerInterval() Function. This function can only be used if the Dynamic Array Functions are being used. It requires a single parameter as its input; the Timer Name that the user gave to the timer when it was created. The function returns the Timer Inverval associated with that Timer in milliseconds.
Function TimerInterval(timerName$)
For i = 0 To UBoundStringArray("TimerQueueTimer")
If (TimerQueueTimer$(i) = "") Then Exit For
If Word$(TimerQueueTimer$(i), 1) = timerName$ Then
TimerInterval = Val(Word$(TimerQueueTimer$(i), 3))
Exit For
End If
Next i
End Function
Summary
Pheeewww!!! That's is about all it takes to make multiple timers. It seems like a lot of work, but trust me once you understand how everything fits together you'll be creating timers left and right in no time.
Below you will find a working demo which uses some of the functions to create three timers that change what is being displayed in the window. I hope this has been at least a little helpful...
{:0)
Brandon Parker
[Program.Introduction]
'_____________________________________________________________________________
'| |
'| Program Introduction |
'|****************************************************************************|
'|*| |*|
'|*| Multi-Timer Routines |*|
'|*| Copyright © 2011 by Brandon Parker |*|
'|*| Written in |*|
'|*| Liberty BASIC v4.04 Copyright © Shoptalk Systems 1992-2010 |*|
'|*| Liberty Basic Home Page at: http://www.libertybasic.com |*|
'|*| |*|
'|*| Utilizing - Dynamic Array Functions |*|
'|*| Copyright © 2010 by Brandon Parker |*|
'|*| Written in |*|
'|*| Liberty BASIC v4.04 Copyright © Shoptalk Systems 1992-2010 |*|
'|*| Liberty Basic Home Page at: http://www.libertybasic.com |*|
'|*| |*|
'|*| You may use/ modify this code in your own projects, |*|
'|*| but may not publish it as your own. |*|
'|*| |*|
'|*| A credit to the original author would be greatly appreciated |*|
'|*| if you use any of these routines. |*|
'|*| |*|
'|*| More about the use of these functions can be found here: |*|
'|*| http://www.microsoft.com/msj/0499/pooling/pooling.aspx |*|
'|*| |*|
'|*| |*|
'|****************************************************************************|
'|____________________________________________________________________________|
[Program.Notes.Warnings]
'_____________________________________________________________________________
'| |
'| Program Notes/ Warnings |
'|****************************************************************************|
'|*| |*|
'|*| - NOTES - |*|
'|*| Note 1: To run this through the DeBugger you will need to |*|
'|*| need to comment out all of the calls to the |*|
'|*| Dynamic Array Functions. |*|
'|*| Note 2: The array code is not exactly necessary, but |*|
'|*| will allow for more flexability if desired. |*|
'|*| Note 3: The DeleteTimerQueueTimerbyName() Function will not |*|
'|*| work without the Dynamic Array Functions. |*|
'|*| Note 4: Not all functions in this demo have been used |*|
'|*| as I have just not implemented anything to use them yet. |*|
'|*| Note 5: These functions require only two variables in the |*|
'|*| callback functions plus the return type. |*|
'|*| |*|
'|*| |*|
'|*| - WARNINGS - |*|
'|*| WARNING 1: The program can not use the WAIT statement at all |*|
'|*| unless you place Scan statements inside the |*|
'|*| CallBack functions that the timers call!!!! |*|
'|*| (The use of WAIT statements is not recommended. |*|
'|*| WARNING 2: The DeleteTimerQueueEx() Function MUST be called |*|
'|*| prior to closing the program window! |*|
'|*| WARNING 3: The ChangeTimerQueueTimer() Function will cause |*|
'|*| the program to crash if it is called with a |*|
'|*| non-existent Timer. |*|
'|*| |*|
'|*| |*|
'|****************************************************************************|
'|____________________________________________________________________________|
[Function.Subroutine.Descriptions]
'________________________________________________________________________________________________________
'| |
'| Function Subroutine Descriptions |
'|*******************************************************************************************************|
'|*| |*|
'|*| - SUBROUTINES - |*|
'|*| |*|
'|*| Name Description |*|
'|*| Quit : This subroutine ensures the proper shutdown of the window. |*|
'|*| |*|
'|*| |*|
'|*| - FUNCTIONS - |*|
'|*| |*|
'|*| Name Description |*|
'|*| WaitorTimerCallBack() : This is the callback function that Timer 1 will fire. |*|
'|*| WaitorTimerCallBack2() : This is the callback function that Timer 2 will fire. |*|
'|*| WaitorTimerCallBack3() : This is the callback function that Timer 3 will fire. |*|
'|*| CreateTimerQueue() : This Function is used to create a Timer Queue for our |*|
'|*| program and returns the handle to that queue. |*|
'|*| CreateTimerQueueTimer() : This Function is used to create timers and returns |*|
'|*| phNewTimer (handle to the timer created) as a pointer |*|
'|*| and returns a number other than 0 if it succeeds |*|
'|*| otherwise 0 if it fails. |*|
'|*| ChangeTimerQueueTimer() : This Function is used to change the DueTime and Period |*|
'|*| of an existing Timer. |*|
'|*| DeleteTimerQueueTimer() : This Function is used to delete a timer from the |*|
'|*| Timer Queue and returns a number other than 0 if it |*|
'|*| succeeds otherwise 0 if it fails. |*|
'|*| DeleteTimerQueueEx() : This Function is used to destroy the Timer Queue and |*|
'|*| all associated timers. |*|
'|*| |*|
'|*| - The following functions require the Dynamic Array Functions to be included. - |*|
'|*| |*|
'|*| TimerInterval() : This function will retrieve the timer inverval associated |*|
'|*| with a specific Timer. |*|
'|*| DeleteTimerQueueTimerbyName() : This Function is used to delete a timer by Name from the |*|
'|*| Timer Queue and returns a number other than 0 if it |*|
'|*| succeeds otherwise 0 if it fails. |*|
'|*| |*|
'|*| - A link to a Liberty Basic Lesson for the Dynamic Array Functions - |*|
'|*| - can be found at the address below. - |*|
'|*| http://libertybasic.conforums.com/index.cgi?board=tips&action=display&num=1269367637 |*|
'|*| |*|
'|*| |*|
'|*******************************************************************************************************|
'|_______________________________________________________________________________________________________|
'First we will create the required Structures
'The first Structure is for holding the handle
'to the created Timer Queue
Struct TimerQueue, handle As ulong
'The Second Structure is required for returning the
'handle of the timer
Struct phNewTimer, handle As ulong
'Now we will define a few flags that can be used.
WT.EXECUTEDEFAULT = _NULL 'By default, the callback function is queued to a non-I/O worker thread.
WT.EXECUTEINTIMERTHREAD = 20 'Passing WT_EXECUTEINTIMERTHREAD in the Flags variable will
'cause the timer to be executed by the calling thread (ie. Your Program)
WT.EXECUTEINPERSISTENTTHREAD = 80 'The callback function is queued to a thread that never terminates.
WT.EXECUTELONGFUNCTION = 10 'The callback function can perform a long wait
'This flag helps the system to decide if it should create a new thread.
WT.EXECUTEONLYONCE = 8 'The timer will be set to the signaled state only once.
'If this flag is set, the Period parameter must be zero.
WT.TRANSFER.IMPERSONATION = 100 'Callback functions will use the current access token,
'whether it is a process or impersonation token
'If this flag is not specified, callback functions
'execute only with the process token.
'Now we open an arbitrary window
'Don't forget the TrapClose Event
[Test.Window.Setup]
NoMainWin
Global True : True = 1
Global False : False = 0
WindowWidth = 270
WindowHeight = 150
StaticText #Test.timer1, "", 90, 25, 100, 25
StaticText #Test.timer2, "", 90, 55, 100, 25
StaticText #Test.timer3, "", 90, 85, 100, 25
Open "Multiple Timers via API" For Window As #Test
Print #Test, "TrapClose Quit"
[CallBack.Setup]
'Here we will create a few callbacks in order to get the
'addresses to the functions we want out timers to call
'|************************************************************|
'|------------------------ See Note 5 ------------------------|
'|************************************************************|
CallBack WaitorTimerCallBackAdd, WaitorTimerCallBack(ULong,ULong),Void
CallBack WaitorTimerCallBack2Add, WaitorTimerCallBack2(ULong,ULong),Void
CallBack WaitorTimerCallBack3Add, WaitorTimerCallBack3(ULong,ULong),Void
[Timer.Queue.and.Timer.Creation]
'We will now set variables for creating the timers
DueTime = 500
Period = 1500
Flags = WT.EXECUTEDEFAULT OR WT.EXECUTELONGFUNCTION
'Here we will create a Timer Queue for the program
TimerQueue.handle.struct = CreateTimerQueue()
'Now we will create our Timers (This can be done at any point in the program; even inside of another timer.)
result = CreateTimerQueueTimer("Timer1", TimerQueue.handle.struct, WaitorTimerCallBackAdd, DueTime, Period, Flags)
result = CreateTimerQueueTimer("Timer2", TimerQueue.handle.struct, WaitorTimerCallBack2Add, DueTime + Int(Period * .3), Period, Flags)
result = CreateTimerQueueTimer("Timer3", TimerQueue.handle.struct, WaitorTimerCallBack3Add, DueTime + Int(Period * .6), Period, Flags)
'We should have a Main.Loop with a Scan to ensure we register user events
'Place a Scan and a tiny sleep in there to allow for
'processing events and to minimize CPU usage. I like
'to use 1ms since then the largest it will actually be is
'the smallest that the kernel can support.
'|************************************************************|
'|----------------------- See Warning 1 ----------------------|
'|************************************************************|
[Main.Loop]
Scan
CallDll #kernel32, "Sleep", 1 As long, ret As void
GoTo [Main.Loop]
'_____________________________________________________________________________
'| |
'|****************************************************************************|
'| FUNCTIONS |
'|****************************************************************************|
'|____________________________________________________________________________|
'Here we have our Functions that the timers will call
Function WaitorTimerCallBack(ULong,ULong)
Print #Test.timer3, ""
Print #Test.timer1, "Timer #1 Fired"
End Function
Function WaitorTimerCallBack2(ULong,ULong)
Print #Test.timer1, ""
Print #Test.timer2, "Timer #2 Fired"
End Function
Function WaitorTimerCallBack3(ULong,ULong)
Print #Test.timer2, ""
Print #Test.timer3, "Timer #3 Fired"
End Function
'____________________________________________________________________________________________________________
'____________________________________________________________________________________________________________
Function CreateTimerQueue()
CallDLL #kernel32, "CreateTimerQueue", CreateTimerQueue As ulong
End Function
'____________________________________________________________________________________________________________
'____________________________________________________________________________________________________________
Function CreateTimerQueueTimer(TimerName$, TimerQueue, WaitorTimerCallBackAdd, DueTime, Period, Flags)
CallDLL #kernel32, "CreateTimerQueueTimer", phNewTimer As struct, _
TimerQueue As ulong, _
WaitorTimerCallBackAdd As ulong, _
parameter As long, _
DueTime As ulong, _
Period As ulong, _
Flags As ulong, _
CreateTimerQueueTimer As long
TimerQueueTimer$(NextAvailableElementStringArray("TimerQueueTimer", False)) = TimerName$ + " " + _
str$(phNewTimer.handle.struct) + " " + _
str$(Period)
End Function
'____________________________________________________________________________________________________________
'____________________________________________________________________________________________________________
'|************************************************************|
'|----------------------- See Warning 3 ----------------------|
'|************************************************************|
'We will keep all of our Timers' handles in TimerQueueTimer$()
Function ChangeTimerQueueTimer(TimerQueue, hTimer, DueTime, Period)
For i = 0 To UBoundStringArray("TimerQueueTimer")
If (TimerQueueTimer$(i) = "") Then Exit For
If hTimer = Val(Word$(TimerQueueTimer$(i), 2)) Then
CallDLL #kernel32, "ChangeTimerQueueTimer", TimerQueue As ulong, _
hTimer As ulong, _
DueTime As ulong, _
Period As ulong, _
ChangeTimerQueueTimer As ulong
Exit For
End If
Next i
End Function
'____________________________________________________________________________________________________________
'____________________________________________________________________________________________________________
Function TimerInterval(timerName$)
For i = 0 To UBoundStringArray("TimerQueueTimer")
If (TimerQueueTimer$(i) = "") Then Exit For
If Word$(TimerQueueTimer$(i), 1) = timerName$ Then
TimerInterval = Val(Word$(TimerQueueTimer$(i), 3))
Exit For
End If
Next i
End Function
'____________________________________________________________________________________________________________
'____________________________________________________________________________________________________________
'CompletionEvent can be _NULL or _INVALID_HANDLE_VALUE (recommend the latter)
Function DeleteTimerQueueTimerbyName(timerName$, TimerQueue, CompletionEvent)
For i = 0 To UBoundStringArray("TimerQueueTimer")
If (TimerQueueTimer$(i) = "") Then Exit For
If Word$(TimerQueueTimer$(i), 1) = timerName$ Then
DeleteTimerQueueTimerbyName = DeleteTimerQueueTimer(TimerQueue, _
Val(Word$(TimerQueueTimer$(i), 2)), _
CompletionEvent)
End If
Next i
End Function
'____________________________________________________________________________________________________________
'____________________________________________________________________________________________________________
Function DeleteTimerQueueTimer(TimerQueue, hTimer, CompletionEvent)
CallDLL #kernel32, "CreateTimerQueueTimer", TimerQueue As ulong, _
hTimer As struct, _
ComletionEvent As ulong, _
DeleteTimerQueueTimer As long
TimerQueueTimer$(Val(SearchStringArray$("TimerQueueTimer", str$(hTimer), "BOA", ""))) = ""
result = CompactStringArray("TimerQueueTimer")
End Function
'____________________________________________________________________________________________________________
'____________________________________________________________________________________________________________
'|************************************************************|
'|------------------------See Warning 2-----------------------|
'|************************************************************|
Function DeleteTimerQueueEx(TimerQueue, CompletionEvent)
CallDLL #kernel32, "DeleteTimerQueueEx", TimerQueue As ulong, _
ComletionEvent As ulong, _
DeleteTimerQueueEx As ulong
result$ = ReDimOrSetStringArray$("TimerQueueTimer", "NULL*", 0)
End Function
'_____________________________________________________________________________
'| |
'|****************************************************************************|
'| DYNAMIC ARRAY FUNCTIONS |
'|****************************************************************************|
'|____________________________________________________________________________|
Function CompactStringArray(arrayname$)
On Error GoTo [Error]
UpperBound = UBoundStringArray(arrayname$)
ReDim temp$(UpperBound)
For i = 0 To UpperBound
If Eval$(arrayname$ + "$(" + str$(i) + ")") <> "" Then
temp$(ii) = Eval$(arrayname$ + "$(" + str$(i) + ")")
ii = (ii + 1)
End If
Next i
result$ = ReDimOrSetStringArray$(arrayname$, "NULL*", (ii - 1))
For i = 0 To (ii - 1)
result$ = ReDimOrSetStringArray$(arrayname$, temp$(i), i)
Next i
CompactStringArray = True
Exit Function
[Error]
CompactStringArray = False
End Function
'____________________________________________________________________________________________________________
'____________________________________________________________________________________________________________
Function NextAvailableElementStringArray(arrayname$, promptuser)
On Error GoTo [Error]
temp$ = " "
Do Until temp$ = ""
temp$ = Eval$(arrayname$ + "$(" + str$(NextAvailableElementStringArray) + ")")
NextAvailableElementStringArray = (NextAvailableElementStringArray + 1)
Loop
NextAvailableElementStringArray = (NextAvailableElementStringArray - 1)
Exit Function
[Error]
UpperBound = UBoundStringArray(arrayname$)
If (NextAvailableElementStringArray - 1) = UpperBound Then
If promptuser = True Then
Confirm "The array " + chr$(34) + arrayname$ + "$()" + chr$(34) +" is full." _
+ chr$(13) + "Would you like to add an element to the array?"; answer$
End If
If (promptuser = True And answer$ = "yes") Or (promptuser = False) Then
NextAvailableElementStringArray = ReDimPreserveStringArray(arrayname$, 1, False)
Else
Notice "You have chosen not to redimension the array!" + chr$(13) _
+ "This may cause your program to respond unexpectedly or crash!"
NextAvailableElementStringArray = UpperBound
Exit Function
End If
Else
NextAvailableElementStringArray = (NextAvailableElementStringArray - 1)
End If
End Function
'____________________________________________________________________________________________________________
'____________________________________________________________________________________________________________
Function ReDimPreserveStringArray(arrayname$, AddNumElements, ShiftRight)
On Error GoTo [Error]
UpperBoundInit = UBoundStringArray(arrayname$)
Dim temp$(UpperBoundInit)
For i = 0 To UpperBoundInit
temp$(i) = Eval$(arrayname$ + "$(" + str$(i) + ")")
Next i
If ReDimOrSetStringArray$(arrayname$, "NULL*", (UpperBoundInit + AddNumElements)) = str$(False) Then
GoTo [Error]
End If
UpperBoundPost = UBoundStringArray(arrayname$)
For i = 0 To UpperBoundPost
If ((ShiftRight = True) And (i <= (AddNumElements - 1))) Or _
((ShiftRight = False) And (i > (UpperBoundPost - AddNumElements))) Then
If ReDimOrSetStringArray$(arrayname$, "", i) = str$(False) Then
GoTo [Error]
End If
If i = AddNumElements Then
ShiftRight = False
End If
Else
If ReDimOrSetStringArray$(arrayname$, temp$(ii), i) = str$(False) Then
GoTo [Error]
End If
ii = (ii + 1)
End If
Next i
GoTo [ExitFunction]
[Error]
ReDim temp$(0)
Notice "Failed to Redimension the array!"
ReDimPreserveStringArray = -1
Exit Function
[ExitFunction]
ReDim temp$(0)
ReDimPreserveStringArray = UpperBoundPost
End Function
'____________________________________________________________________________________________________________
'____________________________________________________________________________________________________________
Function UBoundStringArray(arrayname$)
On Error GoTo [Error]
Do Until UBoundStringArray = -1
temp$ = Eval$(arrayname$ + "$(" + str$(UBoundStringArray) + ")")
UBoundStringArray = (UBoundStringArray + 1)
Loop
[Error]
UBoundStringArray = (UBoundStringArray - 1)
End Function
'____________________________________________________________________________________________________________
'____________________________________________________________________________________________________________
Function ReDimOrSetStringArray$(arrayname$, value$, dimension)
On Error GoTo [Error]
If dimension < 0 Then
dimension = 0
End If
Select Case
Case arrayname$ = "TimerQueueTimer"
If value$ <> "NULL*" Then
TimerQueueTimer$(dimension) = value$
Else
ReDim TimerQueueTimer$(dimension)
End If
Case Else
Notice "The array name " + arrayname$ + "$() passed to the function " _
+ "ReDimOrSetStringArray() does not exist."
GoTo [Error]
End Select
ReDimOrSetStringArray$ = str$(True)
Exit Function
[Error]
Notice str$(Err) + " " + Err$
ReDimOrSetStringArray$ = str$(False)
End Function
'____________________________________________________________________________________________________________
'____________________________________________________________________________________________________________
Function SearchStringArray$(arrayname$, value$, method$, delim$)
On Error GoTo [Error]
method$ = Upper$(method$)
delim$ = Upper$(delim$)
SearchStringArray$ = "NA"
Select Case
Case method$ = "BOA" Or method$ = ""
For SearchStringArray = 0 To UBoundStringArray(arrayname$)
If value$ = Eval$(arrayname$ + "$(" + str$(SearchStringArray) + ")") Then
SearchStringArray$ = str$(SearchStringArray)
Exit Function
End If
Next SearchStringArray
Case method$ = "EOA"
For SearchStringArray = UBoundStringArray(arrayname$) To 0 Step -1
If value$ = Eval$(arrayname$ + "$(" + str$(SearchStringArray) + ")") Then
SearchStringArray$ = str$(SearchStringArray)
Exit Function
End If
Next SearchStringArray
Case method$ = "ALL"
If delim$ = "" Then
Notice "Error: [SearchStringArray$ Function]" + chr$(13) _
+ "The delim$ variable supplied can not be of NULL length."
Exit Function
End If
For SearchStringArray = 0 To UBoundStringArray(arrayname$)
If SearchStringArray = 0 Then
SearchStringArray$ = ""
End If
If value$ = Eval$(arrayname$ + "$(" + str$(SearchStringArray) + ")") Then
SearchStringArray$ = SearchStringArray$ + str$(SearchStringArray) + delim$
End If
Next SearchStringArray
Case Else
Notice "Error: [SearchStringArray$ Function]" + chr$(13) _
+ "The Method$ variable supplied is not a valid method."
End Select
[Error]
Exit Function
End Function
'_____________________________________________________________________________
'| |
'|****************************************************************************|
'| SUBROUTINES |
'|****************************************************************************|
'|____________________________________________________________________________|
Sub Quit handle$
'|************************************************************|
'|----------------------- See Warning 2 ----------------------|
'|************************************************************|
result = DeleteTimerQueueEx(TimerQueue.handle.struct, _INVALID_HANDLE_VALUE)
Close #handle$
End
End Sub