I have the following method that all the error handlers call:
Public Function ToError(strClass As String, strMethod As String) As String
On Error GoTo errHandle
ToError = "Err " & Err.Number & _
", Src: " & Err.Source & _
", Dsc: " & Err.Description & _
", Project: " & App.Title & _
", Class: " & strClass & _
", Method: " & strMethod & _
", Line: " & Erl
Err.Clear
exitPoint:
Exit Function
errHandle:
oLog.AddToLog "Error in ToError Method: " & Err.Description, False
Resume exitPoint
End Function
It turns out that because I declare an error handler in this function On Error GoTo errHandle, VB6 clears the error before I am able to record it.
Is there a way to prevent the 'On Error GoTo errHandle' statement from clearing the error?
An On Error statement will always clear the Err variable (Erl will also be reset to 0). In theory this means you could fix the problem by moving the On Error statement below the ToString = ... line (or removing the error handler in the ToError function altogether), but unfortunately that won't necessarily always work either.
Each component (DLL, ActiveX EXE, etc.) referenced by your project essentially gets its own Err instance in memory. So, if your MainApp.exe raises an error which gets passed to ToError (residing in a separate ErrorHandling.dll for example), the DLL won't see the Err variable that your EXE sees. They each have their own private Err variables.
There are a at least two ways around the problem that I can think of:
Method 1
As Zian Choy mentions, you could add additional parameters to your ToError function, one for each property of the Err object that you need access to.
Code
Public Function ToError( _
ByVal strErrSource As String, _
ByVal nErrNumber As Long, _
ByVal sErrDescription As String, _
ByVal nLineNumber As Long) As String
Example usage
You would then have to call like this from your error handlers like so, passing it all the relevant values from the current Err object, along with Erl:
ToError Err.Source, Err.Number, Err.Description, Erl
If you also want App.Title, you're going to have to add an additional parameter to ToError for that as well, since App.Title will be equal to the App.Title of the project where the ToError method is defined, not the component where the error was raised. This is important if ToError is in a different project.
Method 2
You can make your ToError calls a little less verbose by passing the Err object itself as a parameter to the function, however the first thing your ToError function should do in this case is immediately store a copy of all the relevant properties you need since a subsequent On Error statement will clear the variable.
Code
Public Function ToError(ByVal oError As ErrObject, ByVal nLineNumber As Long) As String
'Copy the important Err properties first, '
'before doing anything else... '
Dim strErrSource As String
Dim nErrNumber As Long
Dim strErrDescription As String
strErrSource = oError.Source
nErrNumber = oError.Number
strErrDescription = oError.Description
On Error Goto errHandle
'More code here
'...
Example Usage
ToError Err, Erl
You may be able to solve the problem by passing the values of the Err object into ToError as parameters.
There's no way to prevent On Error clearing the error.
You could just remove the error handling from ToError. It's so short and bland it's unlikely to ever experience an error.
It might be better to refactor the error handling so that this ToError code is inline in a general purpose error reporting routine, which performs logging or whatever is needed. Then use the techniques in Mike's answer.
BTW If anyone reading this is adding their error handlers manually, stop whatever you are doing and immediately get the free MZ-Tools package.
You can create a User Defined Type as below
Private Type TempErrObj
ErrNumber As Integer
ErrSource As String
ErrDescription As String
End Type
Later modify ToError function as below :
Public Function ToError() As String
Dim iTempErr As TempErrObj
iTempErr.ErrNumber = Err.Number
iTempErr.ErrSource = Err.Number
iTempErr.ErrDescription = Err.Description
On Error GoTo errHandle
ToError = "Err " & iTempErr.ErrNumber & _
", Src: " & iTempErr.ErrSource & _
", Dsc: " & iTempErr.ErrDescription & _
", Project: " & App.Title & _
", Line: " & Erl
Err.Clear
exitPoint:
Exit Function
errHandle:
oLog.AddToLog "Error in ToError Method: " & Err.Description, False
Resume exitPoint
End Function
Related
Theoretically, if VarType(foo) returns 10 or vbError, it means that foo is an error. Am I wrong?
So how do I force this to happen? Is it even possible to force foo to hold an error? If it's not, then I don't understand why Error is a variable subtype.
VBScript doesn't support all of the Variant subtypes. For example, try to create a variable of subtype vbDecimal (VBScript doesn't even have the CDec() function to allow you to type-convert to Decimal) or try to create an array of anything other than the vbVariant subtype.
These constants are part of the Variant specification defined by the OLE Automation libraries. VB/VBA supports all of these subtypes and it seems VBScript adopted the same constants, even though it has no native support for many of them.
In VBScript hold:
If an object has a default property, VarType (object) returns the type of its default property.
The default property of the Err object is Number. Err.Number contains an integer.
Thus, VarType(Err) returns 3 as well as VarType(foo) in next example:
On Error Resume Next
Dim foo
Set foo = Err
se tr 'this line raises an error
Wscript.Echo CBool( IsObject( foo)) _
& vbNewLine & VarType( foo) _
& vbNewLine & foo.Number _
& vbNewLine & foo.HelpFile _
& vbNewLine & foo.HelpContext _
& vbNewLine & foo.Source _
& vbNewLine & foo.Description
I Have Displayed text file in richtextbox.
and onclick on command button value of textbox1 is being replaced in text file.
but How to keep both data . previous one and another which is entered new in textbox
I HAVE USE THIS CODE BUT IT REPLACES ALL THE TEXT :
Open "D:\chat.txt" For Output As #1
a = Text1.Text
Print #1, a
Close #1
Change For Output to For Append, and it will add the new text to the end of the file instead of overwriting it.
Additional note
Since I'm not able to add a comment to Boann's answer (the one marked as accepted).
The Append access mode used with the Print statement automatically appends a new line at the end of the file. This is fine in almost all cases, but for anyone reading this that wants to avoid this behavior, just add a semicolon at the end of the Print statement (this is the only instance I've seen the semicolon used in VB6).
a = Text1.Text
intHandle = FreeFile
Open "D:\chat.txt" For Append As intHandle
Print #intHandle, a; ' Notice the semicolon; prevents a new line after this output.
Close #intHandle
I'm sure the code you posted originally was just for the sake of getting an answer and is not what your code actually looks like. Otherwise:
To you or any future readers, here's a simple AppendToFile() function which will make repeated calls easier, ensures the file gets closed even if a run-time error is encountered, and shows useful debug information upon failure (i.e. with an invalid filename):
How your original code would be written when putting my below function in your code:
AppendToFile "D:\chat.txt", Text1.Text
And here's the function:
Private Function AppendToFile( _
ByRef FilePath As String, _
ByRef Text As String, _
Optional ByVal AppendNewLine As Boolean = True _
) As Boolean
On Error GoTo ErrorHandler
Dim intHandle As Integer
' Get an available file handle to use.
intHandle = FreeFile
Open FilePath For Append As intHandle
' Only use semicolon at end if we do NOT want to append a new line.
If AppendNewLine Then
Print intHandle, Text
Else
Print intHandle, Text;
End If
Close intHandle
intHandle = 0
AppendToFile = True
Exit Function
ErrorHandler:
' Ensure that file is indeed closed.
If intHandle <> 0 Then
Close intHandle
End If
' Show error in debug window (CTRL+G)
Debug.Print _
"Error (#" & CStr(Err.Number) & ") in " & _
"TextToFile( _" & vbCrLf & _
"`" & FilePath & "`, _" & vbCrLf & _
"`" & Text & "`, _" & vbCrLf & _
IIf(AppendNewLine, "`True`", "`False`") & vbCrLf & _
"): " & Err.Description & IIf("." = Right$(Err.Description, 1), "", ".") & vbCrLf
Exit Function
End Function
What is the best way in VB6 to pass an error back to the calling function?
1 On Error Resume Next
2 ' do something
3 If Err.Number <> 3026 Or Err <> 0 Then ?????????
How would you send the error in Line 3 back to the calling function? Is the following the only way to achieve this?
errNum = Err.Number
On Error Goto 0
Err.Raise errNum
Use On Error GoTo and re-raise the error in the handler with Err.Raise.
Private Function DoSomething(ByVal Arg as String)
On Error GoTo Handler
Dim ThisVar as String
Dim ThatVar as Long
' Code here to implement DoSomething...
Exit Function
Handler:
Err.Raise Err.Number, , "MiscFunctions.DoSomething: " & Err.Description
End Function
You'll then be able to get the error number and description in the caller via Err.Number and Err.Description.
If the caller is also using On Error GoTo, you'll see them in the handler there.
If the caller is using On Error Resume Next, then you can still use those same variables inline.
I prefer the first option, using On Error Goto in all functions and subs, because it seems like the natural way to use VB6's built-in error raising features. You can also update the description in the called function's handler, like the example above, and get a pseudo call stack you can eventually log or display to yourself during debugging.
More VB6 error handling thoughts here:
Is it possible to retrieve the call stack programmatically in VB6?
How to clean up error handling in a function?
Why not add ByRef errorCode as Long to the called function's args and set it equal to Err.Number after ' do something
Or you could have a public field called ErrorCode as Long that you could set after ' do something
I have worked with a lot of industrial control APIs and both of these methods have been used.
You can easily send the error to the upper (calling) sub/function as long as the function that raises the error does not have (ON ERROR RESUME ---), that way, error handling is left in the upper level only. Otherwise you will have to handle the error inside the called function
Private Sub Command1_Click()
Dim test As Integer
On Error Resume Next
test = myFunction 'Calling a function that is known to have an error
If Err <> 0 Then
MsgBox "MyFunction failed because:" & Err.Description 'Error is passed
End If
End Sub
'--------------------------
Function myFunction() As Integer
Dim i As Integer
i = 1
i = 4 / 0 'This will raise an Error, and control returns to the calling sub
i = 2 'This will never get executed
myFunction = i
End Function
If you simply want to pass the error back to the original caller without handling it, then you want to remove any ON ERROR in the child function:
Public Sub ParentSub()
On Error GoTo ErrorHandler
' do something
Call ChildSub()
' do something
Exit Sub
ErrorHandler:
' handle the error here
End Sub
Public Sub ChildSub()
' do something
' if there is an error here, the error will be handled in ErrorHandler of ParentSub
End Sub
or if you want to handle it in both subs:
Public Sub ParentSub()
On Error GoTo ErrorHandler
' do something
Call ChildSub()
' do something
Exit Sub
ErrorHandler:
' handle the error here
End Sub
Public Sub ChildSub()
On Error GoTo ErrorHandler
' do something
Exit Sub
ErrorHandler:
' handle the error here and pass it back to the ParentSub to handle it as well
Err.Raise Err.Number
End Sub
I developed an application in VB6. In client's environment it raises runtime errors which I can't reproduce under debugger. Is there any way to get the stacktrace or location of error?
I created log file and
I used
Err.Description,Err.Source
but it gives blank values.
Please help me.
my method(......
On Error GoTo Error_Handler
.........
Error_Handler :
writeToLogFile(Err.Source,Err.Description)
You've probably done something to clear the Err object before writing to the log file. This is very, very easy to do. What you'll want to do is as soon as you detect an error has occurred, grab the error message before doing anything else. Then pass the error message to whatever logging routine you're using. E.g.:
Dim sMsg As String
On Error Goto ErrHandler
' ...code here...
Exit Function
ErrHandler:
sMsg = "Error #" & Err.Number & ": '" & Err.Description & "' from '" & Err.Source & "'"
GoLogTheError sMsg
BTW, thanks for your guys' answers helping me. I'm about half a decade late to the game of VB6. I don't do windows unless forced to. ;)
Anyhow, when doing your error checking, say among 3000 individual record query insertions, I learned a couple tricks. Consider this block of code:
'----- order number 1246-------
On Error Goto EH1246:
sSql="insert into SalesReceiptLine ( CustomerRefListID,TemplateRe..."
oConnection.Execute sSQL
sSql="SELECT TxnID FROM SalesReceiptLine WHERE RefNumber='1246'..."
oRecordset.Open sSQL, oConnection
sTxnId = oRecordset(0)
oRecordset.Close
sSql="INSERT INTO SalesReceiptLine (TxnId,SalesReceiptLineDesc,Sal..."
oConnection.Execute sSQL
EH1246:
IF Err.Number<>0 THEN
sMsg = sMsg & "Order # 1246; sTxnId = " & sTxnId & _
vbCrLf & Err.Number & ": " & Err.Description & vbCrLf
sErrOrders = sErrOrders & "1246,"
End If
On Error GoTo -1
'----- order number 1247-------
On Error Goto EH1247:
When not testing for Err.Number, you'll get a 0: on every order handled. (maybe you don't want that). The On Error GoTo -1 resets the error so that it will work again. Apparently, Err only works "once".
I wrote a php script to build the VB6 source code to run some 8000 odbc queries... :P
Do you definitely, positively have an Exit Function just above the Error_Handler:?
my method(......
On Error GoTo Error_Handler
........
Exit Sub
Error_Handler :
writeToLogFile(Err.Source,Err.Description)
"Exit Sub" should be added before you handle the Error_Handler function.....
I have VB6 application , I want to put some good error handling finction in it which can tell me what was the error and exact place when it happened , can anyone suggest the good way to do this
First of all, go get MZTools for Visual Basic 6, its free and invaluable. Second add a custom error handler on every function (yes, every function). The error handler we use looks something like this:
On Error GoTo {PROCEDURE_NAME}_Error
{PROCEDURE_BODY}
On Error GoTo 0
Exit {PROCEDURE_TYPE}
{PROCEDURE_NAME}_Error:
LogError "Error " & Err.Number & " (" & Err.Description & ") in line " & Erl & _
", in procedure {PROCEDURE_NAME} of {MODULE_TYPE} {MODULE_NAME}"
Then create a LogError function that logs the error to disc. Next, before you release code add Line Numbers to every function (this is also built into MZTools). From now on you will know from the Error Logs everything that happens. If possible, also, upload the error logs and actually examine them live from the field.
This is about the best you can do for unexpected global error handling in VB6 (one of its many defects), and really this should only be used to find unexpected errors. If you know that if there is the possibility of an error occurring in a certain situation, you should catch that particular error and handle for it. If you know that an error occurring in a certain section is going to cause instability (File IO, Memory Issues, etc) warn the user and know that you are in an "unknown state" and that "bad things" are probably going happen. Obviously use friendly terms to keep the user informed, but not frightened.
a simple way without additional modules, useful for class modules:
pre-empt each function/subs:
On Error Goto Handler
handler/bubbleup:
Handler:
Err.Raise Err.Number, "(function_name)->" & Err.source, Err.Description
voila, ghetto stack trace.
I use a home-grown Error.bas module to make reporting and re-raising less cumbersome.
Here's its contents (edited for length):
Option Explicit
Public Sub ReportFrom(Source As Variant, Optional Procedure As String)
If Err.Number Then
'Backup Error Contents'
Dim ErrNumber As Long: ErrNumber = Err.Number
Dim ErrSource As String: ErrSource = Err.Source
Dim ErrDescription As String: ErrDescription = Err.Description
Dim ErrHelpFile As String: ErrHelpFile = Err.HelpFile
Dim ErrHelpContext As Long: ErrHelpContext = Err.HelpContext
Dim ErrLastDllError As Long: ErrLastDllError = Err.LastDllError
On Error Resume Next
'Retrieve Source Name'
Dim SourceName As String
If VarType(Source) = vbObject Then
SourceName = TypeName(Source)
Else
SourceName = CStr(Source)
End If
If LenB(Procedure) Then
SourceName = SourceName & "." & Procedure
End If
Err.Clear
'Do your normal error reporting including logging, etc'
MsgBox "Error " & CStr(ErrNumber) & vbLf & "Source: " & ErrSource & vbCrLf & "Procedure: " & SourceName & vbLf & "Description: " & ErrDescription & vbLf & "Last DLL Error: " & Hex$(ErrLastDllError)
'Report failure in logging'
If Err.Number Then
MsgBox "Additionally, the error failed to be logged properly"
Err.Clear
End If
End If
End Sub
Public Sub Reraise(Optional ByVal NewSource As String)
If LenB(NewSource) Then
NewSource = NewSource & " -> " & Err.Source
Else
NewSource = Err.Source
End If
Err.Raise Err.Number, NewSource, Err.Description, Err.HelpFile, Err.HelpContext
End Sub
Reporting an error is as simple as:
Public Sub Form_Load()
On Error Goto HError
MsgBox 1/0
Exit Sub
HError:
Error.ReportFrom Me, "Form_Load"
End Sub
Reraising an error is as simple as calling Error.Reraise with the new source.
Although it is possible to retrieve the Source and Procedure parameters from the call stack if you compile with symbolic debug info, it's not reliable enough to use in production applications
ON ERROR GOTO
and the
Err
object.
There is a tutorial here.
Yes, take Kris's advice and get MZTools.
You can add line numbers to section off areas of complex procedures, which ERL will report in the error handler, to track down which area is causing the error.
10
...group of statements
20
...group of statements
30
...and so on
Use on
dim errhndl as string
on error goto errhndl
errhndl:
msgbox "Error"
Use the On Error statement and the Err object.