In VB6, I have the following line of code in the Form_Load event:
DOSOMETHING()
MsgBox "Done"
DOSOMETHING() is a buggy function that I expect to always crash. When I run the app, it will do its thing and crash, without showing the MsgBox.
But when I write it using loops:
Dim X as Integer
For X = 0 to 1000
DOSOMETHING()
MsgBox "Done"
Next X
The application will not crash, ever. I thought that this has something to do with delays, so I also tried to add a SLEEP inside the loop, to no avail.
So my question is, Is there a special "On Error Resume Next" inside a For loop in VB6?
PS:
If anyone is curious about why I'm asking this, I am trying to reproduce an intermittent bug by calling the function multiple times. Said function is used to check for Administrator function. More detail about the function here.
Thanks!
I't might have something to do with the fact it's called from Form_Load. Perhaps some initialization later in Form_Load or in Form_Activate causes it not to crash.
Try inserting DoEvents after the call to DoSomething. This yields to the o/s, allowing events in its queue to be processed and may enable the function to complete, or fail! before returning to its calling parent.
Related
I have an old VBScript project I need to debug and due to it's complexity I cannot see where are some functions being called from. Is there any VBScript alternative to PHP's debug_backtrace function or similar to see the stack trace?
So at this point, after a lot of searching, I am going to answer my own question as follows: VBScript is relatively old language and does not seem to have any built-in function/method or other means which would reliably return stack trace of function call (similar to PHP's debug_backtrace() function.
I managed to track the execution path with a simple, custom Function logBreakPointToFile() which I wrote and I placed throughout 100s of .asp files (everywhere where the method in question was referenced directly and indirectly - nested). This gave me a very messy idea what was actually happening. However is cost me a lot of prep. time and clean-up time which I was trying to avoid in the first place.
If anyone finds a better solution, please share.
Thx
VBScript does not throw any exception with stacktrace information in case of error.
#Runtime, in case of error, VBScript throws an Err object.
Ex: Below script will throw 'Division by zero' error & script will not get executed further.
Msgbox 5/0
Msgbox "Hello" 'This will not execute
On Error:
VBScript uses 'On Error Resume Next' statement which starts the error handler. It means, in case of error just skip current line and goto the next line. 'On Error GoTo 0' disables the current error handler. (like you are closing an 'if' block). Please check MSDN for more details.
Consider this example.
On Error Resume Next
Msgbox 5/0
Msgbox "Hello" 'This will say "Hello"
Msgbox Err.Number '11
Msgbox err.Description 'division by zero
On Error GoTo 0 'stops the error handler. any run time error after this will stop the program
Err.Number will be 0 in case no error is found.
So, You can have function like
Function Divide(ByVal A, ByVal B)
On Error Resume Next
Dim Result
Result = A/B
If Err.Number = 0 Then
Divide = Result
Else
Divide = "Infinite"
End If
On Error GoTo 0
End function
Remember this - as it simply goes to the next line in case of error,
On Error Resume Next
If 5/0 Then
Msgbox "???" 'This will get executed. It does not go to Else
Else
Msgbox "Hello" 'It will not show this
End If
On Error GoTo 0
You need to configure IIS to send errors to the browser. Its under Classic ASP settings on IIS 7 - 8. You have the option to send errors to browser on local requests, or all requests. For testing on a dev box usually its set for local. It will tell you what line number you are erroring out on.
As for getting more detail on what functions are slowing your application down. With classic asp you need to write your own timing code to measure such metrics.
I have an Internet Transfer Control on a form called "inetFTP". After I call
inetFTP.Execute , "Get " & "test.zip" & " " & "C:/test.zip"
I want to pause the code execution until the download is finished, so there wouldn't be any other code operating on the file afterwards that could encounter problems. Is there a way to do that?
Normally you'd use the control's StateChanged event and monitor for at least the icError and icResponseCompleted states.
But in real programs it is often necessary to use this along with a Timer control and an elapsed time counter and cancellation flag. You'll want to be sure you don't miss any state changes (some don't seem to fire the event if they occur in quick succession), to handle timeouts, to cancel long running operations, etc.
I suspect there are some long standing bugs in the control that have never been ironed out, which is why StateChanged isn't as reliable as one might hope. Some of this may relate to inherent quirks or race conditions in the session-oriented FTP protocol. HTTP operations seem quite a bit more deterministic.
From there you'd need to change your program flow to properly fit the model of a Windows program.
A long running async operation can be started, but then there is only so much more "worth doing" in most cases until you get a completion signal (or an abort, etc.).
So you do that Execute and then exit the event handler you are running in. Once completion is signaled you resume processing in that completion event handler.
VB6 is not QBasic, and Windows is not DOS.
You can use a Timer (VBA.DateTime.Timer), see below:
Dim PauseTime As Single, start As Single
PauseTime = 2 ' pause the execution of code for two (2) seconds:
start = Timer
Do While Timer < start + PauseTime
DoEvents
Loop
I found the answer. I should insert
Do While inetFTP.StillExecuting
DoEvents
Loop
and this loops until the Internet Transfer Control finishes it job.
What does "DoEvents" do in vb6 ?
Why do I get the error message "Out of stack space" ? What does it mean ?
DoEvents() allows other Windows messages to be processed.
The reason you get an out of stack space error is probably because DoEvents() is allowing events to occur that call your code again, which again calls DoEvents(), and so on until the stack space, which tracks the return addresses for all these calls, has run out.
In general, I do not recommend using DoEvents() due to problems like these and the fact that it violates the overall event-driven design of Windows.
A slightly different way of looking at DoEvents is that it flushes the events in the event queue. If your sub or function triggers an event, that event handler becomes a sub that is in line to run as soon as your sub/function is finished. DoEvents says to run that event handler sub now, instead of waiting till the end of your sub.
While I agree in spirit with Jonathon about not using DoEvents, I would temper his statement by saying I only recommend using it if you know exactly why, and know all of the repercussions of changing the order of the event queue this way. Most often, DoEvents is indicated when you want to update your screen in some way from within the context of a subroutine, before the subroutine is finished executing.
An example of this is when you are using the ProgressBar control. Suppose you are iterating through several thousand records, and want to provide feedback to the user as to how far along you are by updating a progress bar. You might interrupt your loop every hundred records and change the value on the progressbar control. However (unless you do something about it) you won't see the change on the screen until after the progressbar's change event handler runs, and that handler won't run until your sub is done executing. It will just get put in the event queue. The way to force the change event to run immediately, suspending your sub, is to call DoEvents. This will flush all existing events from the queue--in this case your progressbar's change event--and will update the progressbar control on the screen.
Now, "out of stack space" basically means that you've been caught in an endless loop of function calls. The most basic way to cause that is this:
Public sub MySub()
MySub
End Sub
And then call MySub from somewhere. You'll get an out of stack space error. If you look at the Call Stack, you'll see a very long line of calls to MySub.
A well-known real-world example of this would happen in older versions of VB:
Public Sub TextBoxArray_LostFocus(index as Integer)
If TextBoxArray(index) = "" Then
TextBoxArray(index).SetFocus
MsgBox "Please enter a value"
End If
End Sub
This situation assumes two members of a TextBox control array called TextBoxArray. Now, if the user starts with the first one (index 0) and moves to the second one (index 1) then index 0's LostFocus event will fire. However, VB would also internally set the focus to the index 1 box. Then the code would set the focus back to index 0, firing index 1's LostFocus event! You're caught in a loop. They fixed that in VB5 or 6 by waiting to set the focus until the LostFocus event was done executing.
I would clarify Johnathon's answer in that it pumps that VB message loop and allows the VB Runtime to process windows messages, which is the opposite of Sleep which allows for Windows to process its events (not necessary in the world of Multicore CPUs and true multitasking OS's but when VB6 was written Windows 9x was the dominant OS and a hard loop that only had DoEvents in it would spike the CPU usage to 100%). So seeing things like
While fDoneFile = False
DoEvents
Sleep 55
Wend
was a common pattern throughout the VB6 world.
As stated else where, DoEvents allows other events in your application to fire. Here's an example of how you can use DoEvents without the "Out of stack space" issue. This makes sure you don't run through the code multiple times by using a Boolean to indicate the code is running.
Sub Example()
'Create static variable to indicate the sub is running.
Static isRunning As Boolean
'Exit the sub if isRunning
If isRunning Then Exit Sub
'Indicate sub is running
isRunning = True
'Sub does stuff
DoEvents
'Ends up calling sub again
Example 'Added just to prove via testing.
'Indicate sub is no longer runningrunning
isRunning = False
End Sub
I have some code with various "On Error Goto" error handlers in a few places to handle some broken third party hardware. I was getting an overflow error (read from the Err variable) in a routine that doesn't have an error trap but is called by a routine that does. I always thought error traps were only valid in the routine they were declared, but it looks like an error in a subroutine can cause it to go to the calling function's error trap.
So I turned off the calling function's error trap and found my overflow and all is well. But before I did that, I spent some time trying to find a programatic way to get VB to return to its default error handling inside that routine (so I wouldn't have to modify outside code to debug), but I couldn't. The only error commands I could find:
On Error GoTo [label]
On Error Resume Next
On Error Goto 0
On Error GoTo -1
all turn on the manual error handling - is there a way to turn it off (back to the VB6 default)?
This is explained thoroughly in the VB6 manual under Error Handling Hierarchy. On Error Goto 0 disables the error handler in the current procedure, not in the procedures that called it.
If an error occurs in a procedure and
this procedure doesn't have an enabled
error handler, Visual Basic searches
backward through the pending
procedures in the calls list — and executes the first
enabled error handler it finds. If it
doesn't encounter an enabled error
handler anywhere in the calls list, it
presents a default unexpected error
message and halts execution.
As others have said, you can go to Tools-Options-General tab and choose Break on all errors. That effectively disables all your On Error statements - the IDE will break immediately on every error.
That can be irritating if your VB6 code throws errors as part of normal operation. For instance when you check whether a file exists, or when the user presses cancel in a common dialogue. You don't want the IDE to break every time on those lines. But you might have boilerplate error handlers in all your event handling procedures, to stop the program crashing out on unexpected errors. But they are a nuisance when you're debugging problems because the IDE doesn't break on the line with the error. One trick is to switch off those error handlers when running in the IDE, but keep them in the built executable. You do it like this.
Drop these functions into a module.
Public Function InIDE() As Boolean
Debug.Assert Not TestIDE(InIDE)
End Function
Private Function TestIDE(Test As Boolean) As Boolean
Test = True
End Function
Then you can write your error handlers like this.
Private Sub Form_Load()
If Not InIDE() Then On Error Goto PreventCrashes
<lots of code>
Exit Sub
PreventCrashes:
<report the error>
End Sub
Pinched from here. Another tip - use the free add-in MZTools to automatically add these boilerplate error handlers. For production-quality code, you could go further and put an error handler in every routine to create a ghetto stack trace. You might also log the errors immediately in every error handler.
EDIT: Ant has correctly pointed out that On Error Goto -1 is a VB.Net statement and isn't valid in VB6.
EDIT: Arvo and OneNerd have written answers with some interesting discussion of emulating Finally teardown blocks in VB6 error handling. The discussion in this question is also worth a look.
There's clear and simple way to reset error status - use keyword Resume. There are three possibilities:
Resume
Resume Next
Resume <Label>
Resume continues execution at errored line, Resume Next at next line and least talked Resume Label continues at label. Very useful to create try-catch-finally like constructs in VB6. Borrowed and modified from OneNerd answer:
Function MyFunction() as String
'-- start of error block
'
On Error Goto Catch
' do something here that might cause an error
MyFunction = "IT WORKED"
Goto Finally
Catch:
' error occured - do something else
MyFunction = Err.Description
Err.Clear
Resume Finally ''added to clear error status
Finally:
On Error Resume Next ''added to avoid repeated errors
' put your finally code here
'
'-- end of error block
End Function
Simple Err.Clear doesn't help, if some subsequential error occurs in Finally block; Resume Finally does reset internal error state though.
There is a handy right-click menu that lets you turn error handling on and off. Just right-click on a code window and select Toggle, then you can choose "Break on all errors". This will have the effect of disabling all your "On Error" statements.
Here is what I do:
First turn on error handling like this if necessary in your Sub Main() or Sub Form_Load() Sub:
'-- turn on error handling
'
On Error GoTo 0
'
'-------------------------
Now errors will be turned on.
Next, Use the On Error Resume Next and On Error GoTo {label} commands in combination with the Err object. Here is an example of emulating a try/catch/finally:
Function MyFunction() as String
'-- start of error block
'
On Error Goto Catch
' do something here that might cause an error
MyFunction = "IT WORKED"
Goto Finally
Catch:
' error occured - do something else
MyFunction = Err.Description
Err.Clear
Finally:
' put your finally code here
'
'-- end of error block
End Function
/Tools/Options/General/Error Handling
on error goto 0
Should be what you want... It should cause the error to the thrown, and in turn probably unwind up to the RTL...
It's been a long time, but I'm pretty sure that's what you want.
on error resume next
will just continue to the next statement, so you NEED to have plenty of
if err.Number <> 0 then
statements in your code where errors CAN occur...
Have to agree with LarryF, On Error Goto 0 should turn off explicit error-handling that has been turned on by On Error Resume Next. Functions and subroutines do have their own scope for this though. From Dr. Scripto at Microsoft:
Putting On Error Resume Next at the
beginning of the script, as we often
do, makes it apply to the entire body
of the script. But, as we'll see in
later examples, its scope does not
include functions or subroutines. If
you want to handle errors within a
function or subroutine, you must also
include On Error Resume Next in each
of them before checking the Err
object.
You can turn error-handling off with
On Error GoTo 0. So it's possible to
turn error-handling on with On Error
Resume Next just before you want to
check the Err object, and turn it off
after with On Error GoTo 0.
There is no "On Error GoTo -1" so I have no idea where you got that.
VB6 exception handling is covered very thoroughly in the manual.
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.