Visual Basic

Tip 23: Use symbolic debugging information.

For extra tricky debugging, you should check out the Visual Studio debugger (which you get to by starting Visual C++). You obviously need the whole thing, in Visual Studio terms, to use this debugger in Visual Basic (or have a third-party debugger that can use the symbolic debugging information produced by Visual Basic). You'll also need some instructions on using the debugger, as it's one of the least documented features that I've ever seen.

To use the Visual Studio debugger, if you have it, do the following.

Build your Visual Basic application using the Compile To Native Code option. On the Compile tab of the Project Properties dialog box, select the Create Symbolic Debug Info and No Optimization options. Make sure that you build the EXE/DLL/OCX (I'll assume you're debugging a simple EXE from here on in) so that the binary is stored in the same folder as the source code. Start Visual C++, select Open from the File menu, and then select your built EXE file. Select Open from the File menu again (more than once if necessary) to select the source files you want to debug and trace through (FRM, BAS, CLS, etc.). Move the cursor to the line(s) where you want to start your debugging, and hit F9 to set a breakpoint. Once you've done this, hit F5. If you've set a breakpoint on, say, a Form_Load that runs as the program starts, you should immediately have broken to the debugger at this point. One more thing-use F10 to step through your code, not F8.

See your Visual Studio documentation (and Chapter 7) for more on how to use the debugger.

More on Client/Server Error Handling

Components should be nonintrusive about their errors. Instead of raising their own errors in message boxes, or through other UI, directly to the user, the components should pass any error that cannot be handled and corrected entirely within the component to their client's code, where the developer can decide how to handle it.

It is possible and even highly likely that a component you're using is itself a client of other components. What should you do when your component gets an error from one that it's using? Well, if you can't recover from the error, you should encapsulate it. It's bad practice merely to raise the other component's error to your client, since your client may well know nothing about this other component and is just dealing with your component. This means that you need to provide meaningful errors from your own component. Rather than passing up other component's errors or Visual Basic errors to your client, you need to define your own and use these. Public constants or Enums with good names are an excellent way of doing this, since they give a source for all errors in your component, and also should give strong clues about each error in its name.

When defining your own error numbers in components, remember that you should use vbObjectError and that currently Microsoft recommends keeping numbers added to it in a range between 512 and 65535.

Constant vbBaseErr        As long = 512
  Constant ComponentBaseErr As long = vbObjectError + vbBaseErr
  Constant MaxErr           As long = vbObjectError + 65535

Remember that there are two occasions when you can safely raise an error in Visual Basic:

  1. When error handling is turned off
  2. When in a procedure's error handler

It is possible to raise events to pass errors to clients in some circumstances. The standard Visual Basic Data control has an error event, for instance.

Logging errors and tracing procedure stacks in components raises some special problems. It clearly makes sense to log errors, which are sent back to a client from a component. Thus, if you're creating an OCX, you would raise an error and expect that the client code would log that error in its error log. Components on remote machines may also have specific requirements about where to log.

For components there are a number of possible error log locations:

  • Files (either flat files or Structured OLE storages)
  • NT Event Log (but beware of using this with components deployed in Microsoft Transaction Server)
  • Databases (but always have some other means to fall back on if your database connections fail)

It makes sense to add other information to a component's error log, because it's useful to know the UserID or Logon name, the machine name, the process ID, and the thread ID. We frequently use a class for this purpose, which returns the following:

  • Process ID
  • Thread ID
  • Machine name
  • Network version info
  • LanGroup
  • LanRoot
  • Current user name
  • Logon server
  • Domain
  • Number of users currently logged on
  • Other available domains

This information is useful for sorting information in a component's error log.