Handling of Automation errors in VB - vb6

EDIT#1
I am developing a VB6 EXE application intended to output some special graphics to the Adobe Illustrator.
The example code below draws the given figure in the Adobe Illustrator as a dashed polyline.
' Proconditions:
' ai_Doc As Illustrator.Document is an open AI document
' Point_Array represented as "array of array (0 to 1)" contains point coordinates
'
Private Sub Draw_AI_Path0(ByRef Point_Array As Variant)
Dim New_Path As Illustrator.PathItem
Set New_Path = ai_Doc.PathItems.Add
New_Path.SetEntirePath Point_Array
New_Path.Stroked = True
New_Path.StrokeDashes = Array(2, 1)
End Sub
This simple code, however, can raise a variety of run-time automation errors caused by:
Incorrect client code (for example, assigning a value other than Array to the
New_Path.StrokeDashes)
Incorrect client data (for example, passing too large Point_Array to New_Path.SetEntirePath)
Unavailability of some server functions (for example when the current layer of the AI is locked)
Unexpected server behavior
EDIT#2
Unfortunately, since such errors are raised by the server app (AI, in our case) their descriptions are often inadequate, poor and misleading. The error conditions may depend on AI version, installed apps, system resources etc. A single problem can lead to different errors. Example passing too large Point_Array to New_Path.SetEntirePath (Windows XP SP3, Adobe Illustrator CS3):
For array size of 32767 and above, the error is -2147024809 (&H80070057) "Illegal Argument"
For array size of 32000 to 32766, the error is -2147212801 (&H800421FF) "cannot insert more segments in path. 8191 is maximum"
END OF EDIT#2
The traditional error handling can be used to prevent the client crash and to display the error details as shown below:
Private Sub Draw_AI_Path1(ByRef Point_Array As Variant)
Dim New_Path As Illustrator.PathItem
On Error GoTo PROCESS_ERROR
Set New_Path = ai_Doc.PathItems.Add
New_Path.SetEntirePath Point_Array
New_Path.Stroked = True
New_Path.StrokeDashes = Array(2, 1)
Exit Sub
PROCESS_ERROR:
MsgBox "Failed somewhere in Draw_AI_Path1 (" & Format(Err.Number) & ")" _
& vbCrLf & Err.Description
End Sub
As you can see, the error number and error description can be accessed easily. However, I need to know also what call causes the error. This can be very useful for large and complex procedures containing many calls to the automation interface. So, I need to know:
What error happened?
What call caused it?
In what client function it happened?
Objective #3 can be satisfied by techniques described here. So, let’s focus on objectives #1 and 2. For now, I can see two ways to detect the failed call:
1) To “instrument” each call to the automation interface by hardcoding the description:
Private Sub Draw_AI_Path2(ByRef Point_Array As Variant)
Dim New_Path As Illustrator.PathItem
Dim Proc As String
On Error GoTo PROCESS_ERROR
Proc = "PathItems.Add"
Set New_Path = ai_Doc.PathItems.Add
Proc = "SetEntirePath"
New_Path.SetEntirePath Point_Array
Proc = "Stroked"
New_Path.Stroked = True
Proc = "StrokeDashes"
New_Path.StrokeDashes = Array(2, 1)
Exit Sub
PROCESS_ERROR:
MsgBox "Failed " & Proc & " in Draw_AI_Path2 (" & Format(Err.Number) & ")" _
& vbCrLf & Err.Description
End Sub
Weak points:
Code becomes larger and less readable
Incorrect cause can be specified due to copypasting
Strong points
Both objectives satisfied
Minimal processing speed impact
2) To “instrument” all calls together by designing a function that invokes any automation interface call:
Private Function Invoke( _
ByRef Obj As Object, ByVal Proc As String, ByVal CallType As VbCallType, _
ByVal Needs_Object_Return As Boolean, Optional ByRef Arg As Variant) _
As Variant
On Error GoTo PROCESS_ERROR
If (Needs_Object_Return) Then
If (Not IsMissing(Arg)) Then
Set Invoke = CallByName(Obj, Proc, CallType, Arg)
Else
Set Invoke = CallByName(Obj, Proc, CallType)
End If
Else
If (Not IsMissing(Arg)) Then
Invoke = CallByName(Obj, Proc, CallType, Arg)
Else
Invoke = CallByName(Obj, Proc, CallType)
End If
End If
Exit Function
PROCESS_ERROR:
MsgBox "Failed " & Proc & " in Draw_AI_Path3 (" & Format(Err.Number) & ")" _
& vbCrLf & Err.Description
If (Needs_Object_Return) Then
Set Invoke = Nothing
Else
Invoke = Empty
End If
End Function
Private Sub Draw_AI_Path3(ByRef Point_Array As Variant)
Dim Path_Items As Illustrator.PathItems
Dim New_Path As Illustrator.PathItem
Set Path_Items = Invoke(ai_Doc, "PathItems", VbGet, True)
Set New_Path = Invoke(Path_Items, "Add", VbMethod, True)
Call Invoke(New_Path, "SetEntirePath", VbMethod, False, Point_Array)
Call Invoke(New_Path, "Stroked", VbSet, False, True)
Call Invoke(New_Path, "StrokeDashes", VbSet, False, Array(2, 1))
End Sub
Weak points:
Objective #1 is not satisfied since Automation error 440 is always raised by CallByName
Need to split expressions like PathItems.Add
Significant (up to 3x) processing speed drop for some types of automation interface calls
Strong points
Compact and easy readable code with no repeated on error statements
Is there other ways of handling automation errors?
Is there a workaround for the Weak point #1 for 2)?
Can the given code be improved?
Any idea is appreciated! Thanks in advance!
Serge

Think of why it is you might want to know where an error has been raised from. One reason is for simple debugging purposes. Another, more important, reason is that you want to do something specific to handle specific errors when they occur.
The right solution for debugging really depends on the problem you're trying to solve. Simple Debug.Print statements might be all you need if this is a temporary bug hunt and you're working interactively. Your solution #1 is fine if you only have a few routines that you want granular error identification for, and you can tolerate having message boxes pop up. However, like you say, it's kind of tedious and error prone so it's a bad idea to make that into boilerplate or some kind of "standard practice".
But the real red flag here is your statement that you have "large and complex procedures containing many calls to the automation interface", plus a need to handle or at least track errors in a granular way. The solution to that is what it always is - break up your large and complex procedures into a set of simpler ones!
For example, you might have a routine that did something like:
Sub SetEntirePath(New_Path As Illustrator.PathItem, ByRef Point_Array As Variant)
On Error Goto EH
New_Path.SetEntirePath Point_Array
Exit Sub
EH:
'whatever you need to deal with "set entire path" errors
End Sub
You basically pull whatever would be line-by-line error handling in your large procedure into smaller, more-focused routines and call them. And you get the ability to "trace" your errors for free. (And if you have some kind of systematic tracing system such as the one I described here - https://stackoverflow.com/a/3792280/58845 - it fits right in.)
In fact, depending on your needs, you might wind up with a whole class just to "wrap" the methods of the library class you're using. This sort of thing is actually quite common when a library has an inconvenient interface for whatever reason.
What I would not do is your solution #2. That's basically warping your whole program just for the sake of finding out where errors occur. And I guarantee the "general purpose" Invoke will cause you problems later. You're much better off with something like:
Private Sub Draw_AI_Path4(ByRef Point_Array As Variant)
...
path_wrapper.SetEntirePath Point_Array
path_wrapper.Stroked = True
path_wrapper.StrokeDashes = Array(2, 1)
...
End Sub
I probably wouldn't bother with a wrapper class just for debugging purposes. Again, the point of any wrapper, if you use one, is to solve some problem with the library interface. But a wrapper also makes debugging easier.

One would run it in the VB6 debugger. If compiled without optimisation (you won't recognise your code if optimised) you can also get a stack trace from WinDbg or WER (use GFlags to set it up). HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug is where settings are stored.
You can also start in a debugger.
windbg or ntsd (ntsd is a console program and maybe installed). Both are also from Debugging Tools For Windows.
Download and install Debugging Tools for Windows
http://msdn.microsoft.com/en-us/windows/hardware/hh852363
Install the Windows SDK but just choose the debugging tools.
Create a folder called Symbols in C:\
Start Windbg. File menu - Symbol File Path and enter
srv*C:\symbols*http://msdl.microsoft.com/download/symbols
then
windbg -o -g -G c:\windows\system32\cmd.exe /k batfile.bat
You can press F12 to stop it and kb will show the call stack (g continues the program). If there's errors it will also stop and show them.
Type lm to list loaded modules, x *!* to list the symbols and bp symbolname to set a breakpoint
da displays the ascii data found at that address
dda displaysthe value of the pointer
kv 10 displays last 10 stack frames
lm list modules
x *!* list all functions in all modules
p Step
!sysinfo machineid
If programming in VB6 then this environmental variable link=/pdb:none stores the symbols in the dll rather than seperate files. Make sure you compile the program with No Optimisations and tick the box for Create Symbolic Debug Info. Both on the Compile tab in the Project's Properties.
Also CoClassSyms (microsoft.com/msj/0399/hood/hood0399.aspx) can make symbols from type libraries.

Related

Performance impact of calling lots of functions in vbscript

I have lots of sub routines and functions like these:
Sub LogInfo(Txt)
'Write an entry to the log file
If LogEnableInfo Then Log "Info: " & Txt
End Sub
Or this:
Function GetSettingValue(Key)
'Get the value of a setting or an empty string if the setting is not set
Dim Res
If Settings.Exists(UCase(Key)) Then Res = Settings(UCase(Key)) Else Res = ""
GetSettingValue = Res
End Function
Or this:
Sub DoExp(Exp, ErrTag)
'Execute an HS.Exp with Error handling and Debugging
Err.Clear
On Error Resume Next
HS.Exp Exp
If Err.Number <> 0 Then
LogError "HS.Exp """ & Exp & """ , Tag: " & ErrTag
Err.Clear
ElseIf LogEnablePut Then
Log "Put: HS.Exp """ & Exp & """ , Tag: " & ErrTag
End If
On Error GoTo 0
End Sub
They're really one liners that don't do much, but I use lots of times, so I don't want to type it out every time. I also use recursive functions a lot.
Since speed and memory use is critical to my application, what I would like to know is if the act of calling a function or sub in vbscript has a considerable performance impact? I know that in PHP, this has been cited as a performance issue. Can I just go ahead and create zillions of little nested functions to do everything or should I use them a little more sparingly?
In my experience using functions repeatedly is a bad thing in VBS, in a small test script I wrote I found that using functions were about 5 times slower.
startTime = Timer
x = 0
For i = 0 To 1000000000
x = x + 1
Next
endTime = Timer
WScript.Echo x
WScript.Echo CStr(endTime - startTime)
startTime = Timer
y = 0
For i = 0 To 1000000000
y = fooBooGoo(y)
Next
endTime = Timer
WScript.Echo y
WScript.Echo CStr(endTime - startTime)
Function fooBooGoo(p)
g = p + 1
fooBooGoo = g
End Function
The output I got was:
1000000001
306.6289
1000000001
1554.875
The rule "violate good practice (don't repeat yourself, clean program structure, ...) for speed gains!" is bad.
I doubt that there is even one real life VBScript program that becomes fast enough as soon as you inline functions/subs/methods - if you show me two, I'm willing to discuss using/writing a preprocessor for VBScript that expands macros and adds line numbering to scripts.
If speed is important, you should benchmark early/continously and optimize for speed by choosing better algorithms or better tools (COM, Net, Shell).
If (3) does not help, switch to a better language.
Samples to illustrate what I mean by "better tools" (#3):
using one ADO connection (created once (with late binding in VBScript)) to execute many SQL statements on a folder of .CSVs instead of using a FSO, .Readline loops, Splits, and complex IFs
using one .Net component (ArrayList, StringBuilder, ...) for many operations instead of using ReDim Preserve on native arrays or string concatenation in a loop
shelling out to dir /s /b c:\where\ever\*.vbs instead of a (faulty/inefficient) homegrown/stolen recursive directory walker that looks at all files to filter the .vbs
Functions are used to carry on a specific task or to perform specific operation. Same as in other languages, but in OOP they are usually called "methods", at least when they are attached to a class. It means to be able to work. Function is the process where a machine can work and make things efficient. here's an ex. that is used in a sentence. the machine won't work right. Or what you could say is the function is the ability to work so the mathematical definition would be the ability to solve an equation/problem. Function means working.
So, with regard to using function re-cursive's would be better but overall in object based languages like vbscript, objects should be created and set=nothing later for actually making system consume memory efficiently.
Thanks

Error 91 using CreateObject on XP machines

I have an old VB6 app that I've distributed to several users running XP, Windows 7 and Windows 8. The following code is throwing an Error 91 ""Object variable or With block variable not set".
Const ssfPERSONAL = 46 'set directory to the common Documents folder
Dim strMyDocsPath As String 'stores common docsPath
On Error GoTo ErrorHandler
strMyDocsPath = CreateObject("Shell.Application").NameSpace(ssfPERSONAL).Self.Path
Specifically, the last line is the issue. I want strMyDocsPath to point to the common documents folder on the user machine. It works fine in Windows 7 and 8, but not on XP machines except the XP development machine where it runs without a hitch.
On the problem computers, I have tried re-registering scrrrun.dll and got a message it registered successfully. I tried downloading and installing the VB6 distributable SP6 files and still get the error. I have searched several forums and just can't figure it out. Any ideas?
It is usually useful to un-lump complex one-liners in one call per line fashion when debugging such kind of an error:
Dim DebugObj1 As Object
Dim DebugObj2 As Object
Dim DebugObj3 As Object
Set DebugObj1 = CreateObject("Shell.Application")
Debug.Print "1: " & CStr(DebugObj1 Is Nothing)
Set DebugObj2 = DebugObj1.NameSpace(ssfPERSONAL)
Debug.Print "2: " & CStr(DebugObj2 Is Nothing)
Set DebugObj3 = DebugObj2.Self
Debug.Print "3: " & CStr(DebugObj3 Is Nothing)
strMyDocsPath = DebugObj3.Path
Debug.Print strMyDocsPath
After that it is easier to spot what call doesn't return useful object, exactly. In your case it is most likely .NameSpace(ssfPERSONAL).
I was able to reproduce your problem in Windows XP SP3 if Shared Documents are disabled. There are many ways to disable them. See this, for example: http://www.howtogeek.com/howto/windows/how-to-remove-shared-documents-icon-from-my-computer-in-windows-xp/
As a side note, ShellSpecialFolderConstants.ssfPersonal is actually 0x05, see MSDN. The value 46 (or 0x2e) you use is CSIDL_COMMON_DOCUMENTS which indeed translates to Common Documents folder like C:\Documents and Settings\All Users\Documents. Probably, it is not the very best practice to use misleading naming.

VB6 control iteration: one control gives me an error; which one?

I am trying to loop through all controls in a form:
For Each ctrl in Me.Controls
in order enable/disable the control based on some conditions.
But there is a control on the form that gives an error when I try to access it. What kind of control does that, and how do I find it?
When you get your error and click Debug, is the error on the line setting a control's Enabled property?
If so, add a Debug.Print statement writing out the control's name. Do so on the line before setting the Enabled property.
Here's what I mean:
Dim ctrl As Control
For Each ctrl In Me.Controls
Debug.Print ctrl.Name
ctrl.Enabled = True
Next
The Debug.Print statement will write out to the Immediate Window the name of the control that was last processed in the loop, presumably the one that caused your error.
EDIT
This might work. Put this control in a Panel control and set the Panel's Enabled property to False. If I recall correctly, in VB6 setting a container control's Enabled property to False will also set the container's child controls Enabled to False. If your control's Enabled property really is read-only, I'm curious what would happen.
Try this:
Dim ctr As Control
Dim CtrStatus Boolean
CtrStatus = False
For Each ctr In Me.Controls
If (SSTab.hwnd = GetParent(ctr.hwnd)) Then
Call CallByName(ctr, "Enabled", VbLet, CtrStatus)
else
ctr.Enabled = CtrStatus
End If
Next
Another approach is as follows, that also works at runtime (as opposed to just in the IDE):
private sub SetEnabled()
on error goto errHandle
Dim ctrl As Control
For Each ctrl In Me.Controls
ctrl.Enabled = True
Next
exitPoint:
exit sub
errHandle:
MsgBox "Error " & err.Description & " with Control " & ctrl.Name
resume exitPoint
end sub
Suppress the error reports before the loop and then set it back to standard error handling:
On Error Resume Next
For Each ctrl In Me.Controls
ctrl.Enabled = lIsEnabled
Next
On Error GoTo 0
OR name your controls with a standard prefix/suffix that you can check by name and skip in the loop.
For Each ctrl In Me.Controls
If Left(ctrl.Name, 3) = "sst" Then
ctrl.Enabled = lIsEnabled
End If
Next
Tosa: from your comment on AngryHacker's answer, I think you are checking the container incorrectly.
Your code is like this
' BAD CODE '
If ctrl.Container = fraMovies Then
For me that gives error 450 Wrong number of arguments or invalid property assignment. Do you get the same error?
The code should use Is rather than =, like this
' GOOD CODE '
If ctrl.Container Is fraMovies Then
Explanation. You want to check whether two variables "point" to the same control. Controls are objects: you must use Is not = to check whether two object variables "point" to the same object. This is a classic pitfall in VB6.
One last word. Next time, could you try to post 10 lines or less of actual code, reproducing the error, and give the exact error number and message and the exact line on which it occurs? It really does make it much easier for us to solve your problem - I know it's work for you to shorten the code, but you'll get better answers that way.
EDIT Welcome back! :) You said some controls don't have a Container property. You could try wrapping the test in On Error Resume Next, something like this.
' GOOD CODE '
Dim bMatch As Boolean
Dim ctrl As Control
For Each ctrl In Me.Controls
bMatch = False
On Error Resume Next
bMatch = (ctrl.Container Is fraMovies)
On Error Goto 0
If bMatch Then
ctrl.Enabled = True
End If
Next
To avoid such problems follow the given rules while naming contols
When you name an element in your Visual Basic application, the first character of that name must be an alphabetic character or an underscore.
**Begin each separate word in a name with a capital letter, as in FindLastRecord and RedrawMyForm.
Begin function and method names with a verb, as in InitNameArray or CloseDialog.
Begin class, structure, module, and property names with a noun, as in EmployeeName or CarAccessory.
Begin interface names with the prefix "I", followed by a noun or a noun phrase, like IComponent, or with an adjective describing the interface's behavior, like IPersistable. Do not use the underscore, and use abbreviations sparingly, because abbreviations can cause confusion.
Begin event handler names with a noun describing the type of event followed by the "EventHandler" suffix, as in "MouseEventHandler".
In names of event argument classes, include the "EventArgs" suffix.
If an event has a concept of "before" or "after," use a suffix in present or past tense, as in "ControlAdd" or "ControlAdded".
For long or frequently used terms, use abbreviations to keep name lengths reasonable, for example, "HTML", instead of "Hypertext Markup Language". In general, variable names greater than 32 characters are difficult to read on a monitor set to a low resolution. Also, make sure your abbreviations are consistent throughout the entire application. Randomly switching in a project between "HTML" and "Hypertext Markup Language" can lead to confusion.
Avoid using names in an inner scope that are the same as names in an outer scope. Errors can result if the wrong variable is accessed. If a conflict occurs between a variable and the keyword of the same name, you must identify the keyword by preceding it with the appropriate type library. For example, if you have a variable called Date, you can use the intrinsic Date function only by calling DateTime.Date.

How do you make a MsgBox with a "Do Not Ask This Again" or "Don't Ask Me Again" Checkbox in VB6?

I ask this partly because I want to know the best practice way of doing this, and partly because the top google result I got was a forum thread from 2002 in which the question wasn't even answered.
I inherited some VB6 code, and there are some MsgBox calls in said code, many of which display messages that end users would probably find very annoying after a short time (e.g. "Printing Complete," "Record Added," etc.)
I would like to add the standard user interface control of a checkbox on the MsgBox saying "Don't ask me this again", so that when checked and OK is clicked, a setting is saved that lets the program know, you know...to never ask that again. Pretty standard control, the idea is fairly self-explanatory.
What I would like to know is what is the best practice way of doing this in VB6. There is the obvious way of just making a new form for these type of msgboxen and replacing the old MsgBox call with a .Show on that form, but do the VB6 gurus on Stack Overflow have a better way?
Thanks in advance
As far as I know, there is no other way. You need to make your own message box form with the check box. Of course, you'll also need to modify the code to store and retrieve this setting (and acting appropriately based on the setting).
I have done this in my own application many times. One thing to think about.... suppose the user checks the box "don't show me this again". In my opinion, there should be a way to reset the setting. Since the message box form won't show again, I added this to the configuration form (for my app).
One thing you may want to consider is sub-classing the MSGBOX function. You could create a function within your app that has a similar parameter list, but with a couple extra. If the extra parameters are missing, simply call vba.MsgBox (to get the standard behaviour). If you pass in extra parameters, you could call your new form instead.
Well... You are not absolutely correct guys ;)
Starting from Win2000, there's SHMessageBoxCheck function that does the trick.
VB6 declaration:
Private Declare Function SHMessageBoxCheck Lib "shlwapi" Alias "#185" (ByVal hWnd As Long, ByVal lpszText As String, ByVal lpszTitle As String, ByVal dwType As VbMsgBoxStyle, ByVal iDefault As Long, ByVal lpszId As String) As Long
For everything else follow the link :)
If you provide such a functionality, it might require "turning-on" of showing messagebox.
i.e. user should have an option to see the msgbox again using some setting.
Instead of that, you could use statusbar to display notifications OR have a label with the notification messages and turn it off after a few seconds.
This was my down and dirty solution:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
myfile = Workbooks.Application.ActiveWorkbook.Path & "\noprompt.txt"
If Dir(myfile) <> "" Then Exit Sub
exportData = MsgBox("Export data?" & vbCrLf & "Select Cancel (or × top right) to prevent this prompt from displaying again.", vbYesNoCancel, "Close Workbook")
If exportData = vbYes Then
Call ExportValues 'a separate function...
ElseIf exportData = vbCancel Then
'create file noprompt.txt
Open myfile For Output As #1
Write #1, "Delete this file to restore prompt to Export Data when workbook is closed."
Close #1
Exit Sub
ElseIf exportData = vbNo Then
Exit Sub
End If
End Sub
Documentation for my app explains that deleting the file restores the prompt.

Is it possible to retrieve the call stack programmatically in VB6?

When an error occurs in a function, I'd like to know the sequence of events that lead up to it, especially when that function is called from a dozen different places. Is there any way to retrieve the call stack in VB6, or do I have to do it the hard way (e.g., log entries in every function and error handler, etc.)?
You do have to do it the hard way, but it's not really all that hard... Seriously, once you've written the template once, it's a quick copy/paste/modify to match the function name in the Err.Raise statement to the actual function name.
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
When you have nested calls, this unwinds as each routine hits its Handler and adds its name to the error description. At the top level function, you get a "call stack" showing the list of routines that were called, and the error number and description of the error that actually occurred. It's not perfect, in that you don't get line numbers, but I've found that you don't usually need them to find your way to the problem. (And if you really want line numbers, you can put them in the function and reference them in the Err.Raise statement using the Erl variable. Without line numbers, that just returns 0.)
Also, note that within the function itself, you can raise your own errors with the values of interesting variables in the message like so:
Err.Raise PCLOADLETTER_ERRNUM, , "PC Load Letter error on Printer """ & PrinterName & """"
(The syntax highlighting looks wonky in the preview... I wonder how will it look when posted?)
I'm pretty sure you have to do it the hard way. At a previous job of mine, we had a very elegant error handling process for VB6 with DCOM components. However, it was a lot redundant code that had to be added to every method, so much that we had home-grown tools to insert it all for you.
I can't provide too much insight on its implementation (both because I've forgotten most of it and there's a chance they may consider it a trade secret). One thing that does stand out was that the method name couldn't be derived at run-time so it was added as a string variable (some developers would copy-paste instead of using the tool and it would lead to error stacks that lied...).
HTH
The hard, manual way is pretty much the only way. If you check out this question, someone suggested a tool called MZTools that will do much of the grunt work for you.
As other people said (years ago, I see... but there's so many people still using VB6! :) ), I think it's not possible to programmatically retrieve the Call Stack, unless you use some 3rd-party tool.
But if you need to do that for debugging purposes, you can consider of adding to the called routine an Optional input string variable, were you'll put the caller's name.
Sub MyRoutine
(...) ' Your code here
call DoSomething (Var1, Var2, Var3, "MyRoutine")
' ^
' Present routine's name -----------+
(...) ' Your code here
End Sub
Public DoSomething (DoVar1, DoVar2, DoVar3, Optional Caller as string = "[unknown]")
Debug.Print " DoSomething Routine Called. Caller = " & Caller
... ' (your code here)
End Sub
Not so elegant, maybe, but it worked for me.
Regards,
Max - Italy
Compuware (or was it Numega at the time) DevStudio for Visual Basic 6 used to do this. The way was by adding adding instrumenation to every call that called a very small snippet that added to the code stack. On any error it dumped out that callstack, and then did things like mail or post to a webserver all the debuging information. Adding and removing the instrumentation was a potentially lethal operation (especially back then, when we were using VSS as our source control), but if it worked, it work well.
As Darrel pointed out, you could add something very simlar by using MZTools and setting up a template. It's a lot of working, and is probably more effeort than the reward would be but if you have very difficult to track down bugs, it might help).

Resources