I am using WinAPI, SetTimer and KillTimer in my vb6 app (it is legacy project).
SetTimer does return the right event ID but the actual callback doesn't get called at set internval.
I have
....
lngID = SetTimer(0, 0, 3000, AddressOf UpdateCallBack)
Public Sub UpdateCallBack(ByVal hWnd As Long, ByVal uMsg As Long, ByVal idEvent As Long, ByVal dwTime As Long)
......
UpdateCallBack sometimes gets called minutes later ot even hours later.
I heard that the timer is a low priority message and it is handled when there is no other messages to handle.
Is there a way to get the message to be processed?
If it isn't possible with SetTimer, what can I use?
Thanks
Is application doing anything else at the time the callback is expected?
As long as the thread is pumping messages, you should receive the callback very soon after it fires.
If the application is busy, you will need to run the message loop by calling DoEvents.
Have a look a t this thread: http://www.vbforums.com/showthread.php?t=546633
Related
The VCL TMessage class provides the Message, WParam and LParam members, but a window message has more members:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
DWORD lPrivate;
} MSG, *PMSG, *NPMSG, *LPMSG;
Where are the hwnd, time, pt and lPrivate members? I'm in specially need of the time parameter.
Is there a way to access the original message that TMessage is constructed from (or any other means to get the time parameter)?
I'm handling my messages in TComponent::WndProc(Winapi::Messages::TMessage &Message).
Where are the hwnd, time, pt and lPrivate members?
There aren't any such members in TMessage.
MSG is the structure that the Win32 API uses in a message loop to retrieve messages from a message queue, via the GetMessage() and PeekMessage() functions, prior to dispatching them to window procedures via DispatchMessage(). The time, pt, and lPrivate values are not delivered to a window procedure, however a window procedure can retrieve the time and pt values via the GetMessageTime() and GetMessagePos() functions, respectively (the lPrivate value is not accessible).
TMessage is the structure that the VCL uses in window procedures that are created by the RTL's MakeObjectInstance() function. This function allows classes, like the VCL's TWinControl and TTimer, to use non-static virtual WndProc() methods as Win32 window procedures.
In a standard Win32 window procedure, there are only 4 parameters available - hWnd, uMsg, wParam and lParam. An RTL-based window procedure ignores the hWnd (as it already knows exactly which object method to call), copies the uMsg, wParam and lParam values into a TMessage, calls the target WndProc() method passing it the TMessage, and then returns the TMessage::Result value back to the OS.
I'm in specially need of the time parameter. Is there a way to access the original message that TMessage is constructed from (or any other means to get the time parameter)?
If the message comes from the message queue of the thread that is calling your WndProc(), you can use the Win32 API GetMessageTime() function. Or, you can use the Win32 API SetWindowsHookEx() function to install a WH_GETMESSAGE hook into the thread's message queue.
If your component's WndProc() is called in the main UI thread specifically, you can alternatively use the VCL's TApplication::OnMessage or TApplicationEvents::OnMessage events, which receive a copy of the original MSG structure. Your component can use a private TApplicationEvents object to hook the OnMessage event.
However, a window procedure can receive both queued messages and non-queued messages, so if the message does not come from the calling thread's message queue at all, then there is simply no time (or pt) value available to retrieve for it, as non-queued messages do not go through the MSG structure to begin with.
I'm handling my messages in TComponent::WndProc(Winapi::Messages::TMessage &Message).
TComponent does not have a WndProc() method. Perhaps you are thinking of TWinControl::WndProc() instead?
I work on a very old VB6 application which is still our companies main project. I had never worked in Visual Basic much less VB6 before starting on this project. I have run into alot of places that have doevents to keep the form responsive and the more I dig into what doevents does the more I have to scratch my head when attempting to debug code litered with them.
I am working on a way to possibly tell whether a doevent is actually going to do anything when we hit that line of code. I was hoping if some one who has more knowledge of window pumps and the messaging queue could tell me whether my approach will be effective in telling me whether this approach will work.
I am attempting to use PeekMessage to see if their are any messages in the queue right before I hit a doevents line.
Public Declare Function PeekMessage Lib "user32" Alias "PeekMessageA" (lpMsg As msg, ByVal hWnd As Long, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, ByVal wRemoveMsg As Long) As Long
Public Type POINTAPI
X As Long
Y As Long
End Type
Public Type msg
hWnd As Long
Message As Long
Wparam As Long
lParam As Long
time As Long
pt As POINTAPI
End Type
Public Sub PeekAtMessages()
Dim oPeekMessage As msg
Dim bPeekMessage As Boolean
Dim lCtr As Long
bPeekMessage = True
bPeekMessage = PeekMessage(oPeekMessage, frmMain.hWnd, 0, 0, 0)
Debug.Assert bPeekMessage = True
Debug.Print "DoEvents: Message= Hex(" & Hex(oPeekMessage.Message) & ") Dec(" & oPeekMessage.Message & ") hWnd=" & oPeekMessage.hWnd & " lParam=" & oPeekMessage.lParam & " time=" & oPeekMessage.time & " Wparam=" & oPeekMessage.Wparam
End Sub
Public Sub MyDoEvents()
PeekAtMessages
DoEvents
End Sub
So I was hoping that when I call MyDoEvents I would only hit the Debug.Assert bPeekMessage = True if no messages were in the queue. This would allow me when working through old uncommented code to tell whether a doevents actually did something.
I tested this out and it looked like I only hit the Debug.Assert when there are no events but I still don't know if my findings are acurate/reliable. Has anyone else had experience with attempting to view what at doevent is about to do?
The thing to remember is that DoEvents is a magical word. People who have vague or no idea what it does randomly try it in case it works.
VB6 clears it's own message queue, then calls Sleep(0), which means all other apps get to clear their queues.
Of course VBRuntime interupting your code and executing VB6 events has the same problems that multithreading have, global/static variables can be accessed, partially updated, etc. I've never seen synchronisation around a DoEvents statement or that any thought has been given to it.
Spy++ window message spyer is included with VB6 and SDK.
In Windows some messages are generated on demand. Timers, Paint, and a few others aren't actually in the queue. If the queue is empty and the program queries then Windows looks to see if the paint or other flags are set. If so, generates a message and clears that flag. So Paint etc are FLAGS not messages in a queue.
I have some code that collects all the window handles into an array. The problem is, since the callbacks are done asynchronously, i dont know when the enumeration is actually finished. What is the best approach to solving this? I assume this is a common problem in windows programming.
BOOL CALLBACK enumWindowsProc3(HWND hwnd, // handle to parent window
LPARAM lParam // application-defined value
) {
//add hwnd to array
return TRUE;
}
int howManyWindows() {
EnumWindows (enumWindowsProc3, 0);
// need to wait here for EnumWindows() to finish...
return array size
}
I'm not a Windows API expert, but this is a general programming problem.
If you have access to a semaphore type structure that can wait(),signal(),and give you a count of items, then you can block the thread until a certain count is reached. Each asynchronous query to the window would then signal() to the semaphore.
Otherwise you can do a general poll checking the completion of all items but make sure that the thread goes to sleep for a certain amount of time while in the poll loop.
EnumWindows callbacks are synchronous, not async. No wait is required. Code after EnumWindows will execute only after all callbacks have been completed...
Is it possible to cancel out of a long running process in VB6.0 without using DoEvents?
For example:
for i = 1 to someVeryHighNumber
' Do some work here '
...
if cancel then
exit for
end if
next
Sub btnCancel_Click()
cancel = true
End Sub
I assume I need a "DoEvents" before the "if cancel then..." is there a better way? It's been awhile...
Nope, you got it right, you definitely want DoEvents in your loop.
If you put the DoEvents in your main loop and find that slows down processing too much, try calling the Windows API function GetQueueStatus (which is much faster than DoEvents) to quickly determine if it's even necessary to call DoEvents. GetQueueStatus tells you if there are any events to process.
' at the top:
Declare Function GetQueueStatus Lib "user32" (ByVal qsFlags As Long) As Long
' then call this instead of DoEvents:
Sub DoEventsIfNecessary()
If GetQueueStatus(255) <> 0 Then DoEvents
End Sub
No, you have to use DoEvents otherwise all UI, keyboard and Timer events will stay waiting in the queue.
The only thing you can do is calling DoEvents once for every 1000 iterations or such.
Is the "for" loop running in the GUI thread? If so, yes, you'll need a DoEvents. You may want to use a separate Thread, in which case a DoEvents would not be required. You can do this in VB6 (not simple).
You could start it on a separate thread, but in VB6 it's a royal pain. DoEvents should work. It's a hack, but then so is VB6 (10 year VB veteran talking here, so don't down-mod me).
Divide up the long-running task into quanta. Such tasks are often driven by a simple loop, so slice it into 10, 100, 1000, etc. iterations. Use a Timer control and each time it fires do part of the task and save its state as you go. To start, set up initial state and enable the Timer. When complete, disable the Timer and process the results.
You can "tune" this by changing how much work is done per quantum. In the Timer event handler you can check for "cancel" and stop early as required. You can make it all neater by bundling the workload and Timer into a UserControl with a Completed event.
This works well for me when I need it. It checks to see if the user has pressed the escape key to exit the loop.
Note that it has a really big drawback: it will detect if the user hit the escape key on ANY application - not just yours. But it's a great trick in development when you want to give yourself a way to interrupt a long running loop, or a way to hold down the shift key to bypass a bit of code.
Option Explicit
Private Declare Function GetAsyncKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
Private Sub Command1_Click()
Do
Label1.Caption = Now()
Label1.Refresh
If WasKeyPressed(vbKeyEscape) Then Exit Do
Loop
Label1.Caption = "Exited loop successfully"
End Sub
Function WasKeyPressed(ByVal plVirtualKey As Long) As Boolean
If (GetAsyncKeyState(plVirtualKey) And &H8000) Then WasKeyPressed = True
End Function
Documentation for GetAsyncKeyState is here:
http://msdn.microsoft.com/en-us/library/ms646301(VS.85).aspx
Here is a pretty standard scheme for asynchronous background processing in VB6. (For instance it's in Dan Appleman's book and Microsoft's VB6 samples.) You create a separate ActiveX EXE to do the work: that way the work is automatically on another thread, in a separate process (which means you don't have to worry about variables being trampled).
The VB6 ActiveX EXE object should expose an event CheckQuitDoStuff(). This takes a ByRef Boolean called Quit.
The client calls StartDoStuff in the ActiveX EXE object. This routine starts a Timer on a hidden form and immediately returns. This unblocks the calling thread. The Timer interval is very short so the Timer event fires quickly.
The Timer event handler disables the Timer, and then calls back into the ActiveX object DoStuff method. This begins the lengthy processing.
Periodically the DoStuff method raises the CheckQuitDoStuff event. The client's event handler checks the special flag and sets Quit True if it's necessary to abort. Then DoStuff aborts the calculation and returns early if Quit is True.
This scheme means that the client doesn't actually need to be multi-threaded, since the calling thread doesn't block while "DoStuff" is happening. The tricky part is making sure that DoStuff raises the events at appropriate intervals - too long, and you can't quit when you want to: too short, and you are slowing down DoStuff unecessarily. Also, when DoStuff exits, it must unload the hidden form.
If DoStuff does actually manage to get all the stuff done before being aborted, you can raise a different event to tell the client that the job is finished.
EDIT it turns out the MSDN article is flawed and the technique DOESN'T WORK :(
Here's an article on using the .NET BackgroundWorker component to run the task on another thread from within VB6.
I can correctly setup up a windows hook, but I get confused by the line in MSDN that says "Calling the CallNextHookEx function to chain to the next hook procedure is optional, but it is highly recommended; otherwise, other applications that have installed hooks will not receive hook notifications and may behave incorrectly as a result. You should call CallNextHookEx unless you absolutely need to prevent the notification from being seen by other applications.".
I want to be a good programming citizen and call the next hook. But, my hook procedure looks like this:
LRESULT CALLBACK CBTProc(int code, WPARAM wp, LPARAM lp)
{
if (code != HCBT_CREATEWND)
{
// What do I do here? It's not the event I requested so how do I pass it on?
return 0;
}
// It's the code we want (create window)
CallNextHookEx(...);
...
}
So, what happens in the hook procedure if the code isn't the one I'm interested in? How do I call the next hook?
Edit: The main problem is that a HHOOK is returned from the SetWindowsHookEx, and that needs to be passed to the CallNextHookEx function.
Update: It seems the hook parameter is ignored on the NT platforms:
http://msdn.microsoft.com/en-us/library/ms644974.aspx
http://www.klenotic.com/pl/null_hhook/
According to the docs, the proper thing to do is pass the arguments you received directly to CallNextHookEx, exactly as you received them. You should also call CallNextHookEx regardless of whether you decided to handle the hook message.
According to MSDN, the first parameter to CallNextHookEx is ignored on on NT/XP/2003, and for older Win95-based operating systems it should be the HHOOK you received when you registered your hook with SetWindowsHookEx. The docs don't specify a value for Windows 2000, but since it's part of the NT family, a reasonable guess is that it's ignored there as well.
Given all that, a good way to code the method for NT-family operating systems might be this:
LRESULT CALLBACK CBTProc( int code, WPARAM wp, LPARAM lp )
{
if( code == HCBT_CREATEWND )
ProcessCreateWnd( wp, lp );
return CallNextHookEx( 0, code, wp, lp );
}
void ProcessCreateWnd( WPARAM wp, LPARAM lp )
{
// my code here
}
This makes sure that you always call the hook at the end of your processing, and makes it hard to accidentally add a return that will bypass CallNextHookEx.