This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


MIND


This article assumes you're familiar with ASP, Visual Basic

Bulletproofing Your ASP Components
Charles Alexander

Download the code (39KB)

Runtime ASP errors can bring even the most experienced developer to tears. Instead of pulling your hair out trying to nail down the problem, here's a solution that can isolate some of the toughest bugs.
One of the least enjoyable parts of programming—something that all of us have experienced at least once—is getting a call from a user informing us of a runtime error in one of our programs. You often spend more time finding and fixing these errors than you did developing the original program. The problems associated with these errors—and all of the version control issues involved in distributing the corrected code to possibly thousands of users—is a major incentive for many IS departments to make the move to Internet/intranet application development. While having your code able to reside on a single Web server eliminates the distribution headache, failing to catch runtime errors can still bring your server to its knees. Let's go over a technique that can virtually eliminate the problem of runtime errors, adding several magnitudes of stability and scalability to your Active Server Page (ASP) applications.

    How many of you have seen the dreaded ASP0115 error show up in the browser, and not been able to figure out where it came from? I know that I've seen my fair share of these self-inflicted wounds, even more so as I migrate more and more of my ASP pages into ActiveX® components written in Visual Basic®. Until recently, that is.

    Since implementing the programming scheme that I am going to share with you, I have eliminated all ASP0115 errors in my Web applications. That's right—I said all. In addition to that, I have been able to identify and fix error conditions in minutes rather than hours. When a runtime error does happen, the users are usually not even aware of it. However, the programming staff is notified of the nature of the runtime error and its exact location in the source code. I can do this all with CallStack, a component I developed for Visual Basic and ASP. CallStack can be downloaded from the link at the top of this article to help you follow along with this article.

Calling All Stacks

      The programming scheme that allows you to bulletproof your components involves three commandments:

  • Thou shalt encapsulate all code within an error trap.
  • Thou shalt maintain a call stack.
  • Thou shalt number all significant lines of code.
      Consider the sample subroutine in Figure 1, which illustrates the first two commandments. These rules ensure that every procedure in each component is surrounded by code that sets an error handler. All errors are handled by calling the RunTimeError method in the CallStack component. This method uses the parameters sent to it to build an error message that will be written to the Windows NT® Application Event log. Included in the message will be the complete contents of the call stack—all nested procedures and their parameters that have been called right down to the procedure where the error occurred.

    Using the CallStack component is straightforward. Once you compile and register the CallStack component, you can add a reference to it in your ActiveX DLL project. Then add the following line to the Declarations section of your component's class module:


 Dim mCallStack as CallStack2.clsCallStack
In the Class.Initialize procedure of your component, add the following lines of code:

 Set mCallStack = New CallStack2.clsCallStack
 mCallStack.Depth = 10
 mCallStack.AppName = App.Title
 mCallStack.ReportType = NTEvent
 mCallStack.ModuleList = "clsMyComponent^"
 mCallStack.ProcList = "Proc1^Proc2^...^"
 mCallStack.InitStack
Finally, in the Class.Terminate procedure of your component, add this code:

 Set mCallStack = Nothing
This is all the code you need to set up your project to use the CallStack component.

Under the Hood

      Think of CallStack as a stack of plates you see in one of those spring-loaded dispensers at dinner buffets. The guy from the kitchen pushes clean plates onto the stack. The dinner patrons pop the plates off the stack as they serve themselves. CallStack works the same way. As your component enters a procedure, you push an element onto the stack. If you have several procedures nested, additional elements are pushed onto the stack for each nested procedure. As the component leaves each procedure, its corresponding element is popped off the stack.

    The CallStack component is actually an array of structures called StackElem:


 Private Type StackElem
    Module As Integer
    Procedure As Integer
    Params As String
 End Type
Each element on the stack contains the module and procedure index and the parameters. The Push method does nothing more than transfer its three parameters to the next available stack element.

    CallStack starts out with a predefined number of elements, controlled by the Depth property. So in the previous sample code, I started the stack out with 10 predefined elements. (An internal variable in CallStack, miStackPtr, controls which is the next element to load with data.) Under most circumstances, a depth of 10 elements should be more than enough. However, the code within CallStack will initialize additional elements in groups of 10 as needed. In other words, it will grow dynamically.

    If you end up liking CallStack as much as I do, you may use it in all of your components. (It even makes writing Windows NT-based services in Visual Basic a breeze!) That being the case, you need a way to differentiate error events from each component when you use CallStack. That is where the AppName property comes in. Simply set it to the App.Title property of your project, and the Windows NT Event log will identify which component hit a runtime error.

    What if you want this to work with Windows® 95 and Windows 98, or you prefer not to use the Windows NT Event log even in Windows NT? Take a look at the ReportType property. This is enumerated to allow you to send the messages to the event log or a log file. If you choose the log file, use the LogFilePath property to define the full path and file name of the file to which you want error messages written.

    The last two properties, ModuleList and ProcList, are merely a convenience to save typing and reduce the size of the binary component. The Push and Pop methods refer to the positions of your module and procedure names within these two properties, so you don't have to repeat the text twice in each procedure. Note that each module or procedure name is followed by a caret. This is very important. The internal workings of CallStack assume that these two properties are laid out in this fashion. If you fail to do this, your error messages will contain unexpected output.

Putting it into Practice

       Figure 2 contains the code for a sample program illustrating the workings of CallStack. The Main subroutine passes two strings to Proc1, which passes those strings to Proc2, which in turn passes the same strings to Proc3. Proc3 then attempts to add these two strings together, which generates a type mismatch error.

    When you run this program, nothing seems to happen. The program completes normally, and there are no message boxes telling you that you botched it. However, if you examine the file error.log that's found in the directory the program was run from, you will see what really happened. Figure 3 shows the output generated by CallStack. Note that it tells you the exact location and nature of the runtime error. In addition, the values of each passed parameter—all the way to the beginning of the program—are spelled out in the CallStack portion of the output.

Windows NT Events

      The LogNTEvent procedure and its associated function declarations is the only code in this article that I cannot take full credit for. The source for this is derived from article Q154576 in the MSDN® Knowledge Base, "Write to the Windows NT Event Log from Visual Basic" (http://support.microsoft.com/support/kb/articles/
Q154/5/76.asp
).

    All error events are placed in the Application Log by the following line of code:


 LogNTEvent sMsgTxt, EVENTLOG_ERROR_TYPE, 1000
This will generate a Windows NT event with a stop sign icon in the first column. The project name of the component will identify the source of the error. I use a generic event ID of 1000 for all errors. If you would like to modify the source code to produce different event IDs for different conditions, feel free. But in my experience, there is so much detail provided in the text of the event that the event ID is superfluous.

Line Numbers?

      I've only glossed over the third commandment so far, but this is actually the most important one. CallStack by itself can get you to the offending procedure, but you need line numbers to narrow the search to the exact line of code where the runtime error occurred.

    Take another look at the RunTimeError method calls in Figure 2, particularly the last parameter. This uses the Visual Basic Erl keyword, though I challenge you to find where this has been documented since the days of Microsoft QuickBasic®. (Actually, I suspect that Visual Basic 6.0 still contains the entire codebase of the paper-tape version of BASIC that started off Microsoft in the 70s.) But it's a good thing this still exists since it allows you to pass the line number of the error to the reporting mechanism.

    Does this mean that you must discipline yourself to put line numbers on each line of your code? No, not if you use the Line Numbers add-in, whose source code and executable are also available as part of the download at the top of this article.

    If you are using Visual Basic 6.0, you will be able to view and modify the source code. The project uses the Add-In Designer that comes with Visual Basic 6.0 and creates an ActiveX DLL. Once registered, it will automatically appear in the Add-In Manager, as shown in Figure 4.

Figure 4: Visual Basic Add-In Manager
      Figure 4: Visual Basic Add-In Manager

      The Line Numbers add-in attaches itself to the Tools menu in Visual Basic in the AddinInstance_OnConnection event, and gives you four functions to choose from: Add line numbers, Remove line numbers, Format Text, and Sort Procedures. Figure 5 shows the layout of the Line Numbers menu.
Figure 5: Line Numbers Menu
      Figure 5: Line Numbers Menu

      The Line Numbers add-in uses the Microsoft® Visual Basic 6.0 Extensibility library (vb6ext.olb) to gain access to the VBProject, VBComponent, and CodeModule objects. The pivotal code in the add-in may be found in the HandleEvent procedure, which basically consists of the following loop:

 For Each ThisProject In VBInstance.VBProjects
     For Each ThisComponent In ThisProject.VBComponents
 
         Set ThisCode = ThisComponent.CodeModule
 
         ' process all code for this module
    
         Set ThisCode = Nothing
     Next
 Next
      While processing a code module, you use the CountOfDeclarationLines and CountOfLines properties to determine the starting and ending points. (You want to stay away from the Declarations section of the modules.) For each procedure, you use the ProcOfLine property to find the procedure name and type, then the ProcBodyLine, ProcStartLine, and ProcCountLines properties to calculate where the code for the procedure starts and ends.

    For each line in the procedure, you either add or remove the line number or simply format the text with the proper indentation. Then, use the ReplaceLine method to store your changes back into the code.

    If you examine the AddNum procedure, you'll see that it compares the first word of each line against a list of Visual Basic keywords. This eliminates line numbers on lines that either should not or need not be numbered. It also examines such things as comment marks, labels, and continuation markers in order to handle the indentation properly. Finally, there is code to handle a weird compilation error that occurs if the first Case statement directly after a Select Case has a line number.

    The SortProcedures_Click event is comparable to the HandleEvent procedure, but is concerned with procedures as a whole rather than the lines within the procedures. It scans through all procedures within a component, creating arrays of procedure names and types, starting positions, and lengths. I then use a simple swap-sorting algorithm to resort the arrays by procedure name, and use the sorted arrays to rebuild the code module in the proper sequence. Hardly elegant—but quite fast.

In Closing

      Using these two components—CallStack and Line Numbers—you will find it much easier to make your Web server stable and scalable. When errors do occur, as they inevitably will, your components will be able to handle them gracefully, returning at least partial output to the browser, while telling you exactly where to look to fix the problem.

    With these components in your tool belt, you will even be able to tackle the last bastion of system programming—Windows NT-based services. Microsoft does not recommend using Visual Basic to create services, mainly because services must have no possibility of producing uncontrolled UI output, such as runtime error messages. But consider this: how is a Windows NT service different from a component used by Microsoft Internet Information Server? If you can confidently create ASP error-free components in Visual Basic, Windows NT-based services cannot be far behind.

MSDN
http://msdn.microsoft.com/library/
sdkdoc/certsrv/crtsv_using_465f.htm

and
http://msdn.microsoft.com/library/
books/advnvb5/html/ch01_3.htm

From the August 1999 issue of Microsoft Internet Developer.