In this article I show you my ordinary code structure for COM+ components. I show this in preparation for further articles about debugging tips. When I show this example, I guess I will also get a lot of flaming mails since everybody has different opinions on this topic…
In my latest article at VB-2-The-Max An error that must be trappedI promised to continue with a couple of debug-related articles. Before I do that, I’d like to show an example of my typical code structure. It’s very much debug-centred so it’s not totally off the topic. I believe that I have to show it to you before I dig into my debug tips because they rely on this structure. If I start discussing the debug tips before showing the structure, the risk is that you wouldn’t be able to “see the forest for all the trees”.
Before I continue I’d like to tell a little story. Right now I’m working on renovating a room in our house. Yes, I’m doing the work myself. No, it’s not a good idea, but my wife asked if we shouldn’t hire a pro, so I didn’t have a choice. Why am I telling you this? Well, as always, the preparations are very important for the end result, for example when you are re-papering the walls. It’s exactly the same when it comes to system development. Especially when you’re building distributed applications. If you don’t prepare up front for debugging for example, you will suffer in the end.
I have developed and used several proposals for code structure, but the one I’m going to show here today is what I like the best (at least today). I also know that Michael D. Long uses a similar structure so I will ask him for help to find arguments when you start to complain. I will also try not to duplicate comments made by Mike in his article Building a Better Mousetrap for COM+ (MTS) from last week.
You don’t have to like all of it. I myself just love to look at code from other developers because I always get inspiration for how to refine my own. Perhaps you will get one or two such ideas yourself by looking at my code.
I have often written disclaimers for my error trapping and code structure in articles. Since the purpose of those articles has been others, I have chosen not to show the error trapping code and so on. Today, I’m trying to fix that old problem by doing things the other way around. The code shown here today won’t do anything useful at all.
But as always, there is a disclaimer here too. Don’t use any code you find in articles without testing it out thoroughly. That goes here too of course.
OK, let’s start with the code. I will show you a piece at a time and then comment on it.
Private mobjCtx As ObjectContext
As you can see above, I choose to implement the
ObjectControl interface and I keep a module-global variable called
mobjCtx for the
ObjectContext. You could call
GetObjectContext() whenever you need a context-object, but I prefer this style.
I have also chosen to implement a user-defined interface called
ISomething, but that is only to give you an example of how this is handled in this proposal. I will not use the default interface for the class, but will always call the component through this user-defined interface instead.
Private Sub ObjectControl_Activate()
Set mobjCtx = jnskActivate(TypeName(Me))
Private Sub ObjectControl_Deactivate()
jnskDeactivate TypeName(Me), mobjCtx
The only thing I always do in
ObjectControl_Activate is to set the
mobjCtx variable. I hide the call to
GetObjectContext() in my own function called
jnskActivate. The reason for this is amongst others to have a central place for adding tracing. (I will discuss this a lot in greater depth in an upcoming article.) Note that I use
TypeName(Me) as a parameter. This lets me tell
jnskActivate which class the call was made from. Earlier I used a constant for each module (both class modules and code modules) with the name of the module, but last spring Francesco Balena told me about this call that I had totally missed. Thanks Francesco! You could also use a module-global variable and set it once, but then… Sorry, let’s not get stuck with small details now.
jnskDeactivate will set the
Nothing. You should always clean up after yourself, right? I won’t show you my implementations for jnsk-subs/functions today, but you can easily build the basic stuff in them on your own. You will find my implementations in coming articles. Stay tuned!
Of course, there must be an implementation of
ObjectControl_CanBePooled also, but I only return
Public Function ISomething_SomethingFix() As Long
ISomething_SomethingFix = SomethingFix()
As I said before, I use a user-defined interface called
ISomething for calling the methods of this component. I don’t like to have the implementation code in the interface procedure itself. Compare with how you take care of events in the user interface. Most developers don’t think it’s good to have all the code directly in the click-event for a command button. Instead they will call another function/sub from the click-event. There is of course a cost involved in doing it like this, but this redirection is cheap and it also creates a place for putting in code to change output from Recordset to XML, for example. There is also a cost involved in not using the default interface, but once again, in my opinion the benefits outweigh this small runtime overhead. (Well, there are other drawbacks too. When it comes to script-clients, they can only reach the default interface.)
Private Function SomethingFix() As Long
Dim strSource As String
Dim lngErr As Long
Dim strError As String
Dim lngErl As Long
strSource = TypeName(Me) & ".SomethingFix"
On Error Goto ErrorHandler
Above, you have the beginning of the function that will be called. Note that it is private and can in the class only be called from the interface procedure above.
As you can see, I have a variable in each method that holds information about where I am. Unfortunately I can’t ask VB for help with the name of the method so I have to add that myself.
Another comment is that I have all the variables for holding error information locally. If you haven’t heard about the danger of using global variables in VB/COM+, please read MTS (and COM+) and global variables. You could also use module-global variables, but I think this proposal is less bug-risky.
Finally I activate error trapping, and I always use a label called
ErrorHandler. (If you like to write tools for working with your own code, you will love standardised placeholders such as this!)
It’s of course possible to add source information to the interface function too, but I usually skip that.
Let’s take a look at the code flow, when everything works out as expected.
'Do some work...
On Error Resume Next
'Clean up, for example ADO-stuff.
If lngErr = 0 Then
jnskSetCompleteMTS strSource, mobjCtx
As you “know”, we will get
Finally semantics in VB.NET, but until then I try to simulate the same stuff with VB’s old error handling scheme. (I know that I’m not achieving that fully when it comes to clear syntax, but the functionality is quite close.) The
ExitHandler label will always be used at the end of the methods flow. Therefore you have just one place to put your clean-up code. It’s good practice to clean up after yourself. It’s also a good way of avoiding strange problems. A classical example has been (is?) that if ADO-objects weren’t closed explicitly, they would empty the error object, when VB closed the ADO-objects implicitly. Before I start cleaning up, I say that I don’t care about new errors.
After cleaning up, I take a look at the local error variable
lngErr. If it’s zero, everything is OK, and it’s time for
SetComplete. How can
lngErr not be zero you ask? Well, be patient and you will soon find the answer.
Once again, I have my own sub for accomplishing a task. This time it’s the
SetComplete that will be taken care of by
jnskSetCompleteMTS. As you might have guessed, I have several arguments for doing this, such as:
- It gives a nice and central place for adding “custom interception”, such as trace calls. (I’m sorry for nagging, but this will be covered further in another article.)
- It moves the need for conditional compilation sections out of the regular code.
- It’s a preparation for a possible need in the future.
The following code will run when the code reached an error.
jnskSetAbortMTS strSource, mobjCtx
On Error Goto 0
Select Case lngErr
jnskErrRaise doErrUnexpected, lngErr, _
strError, lngErl, strSource, mobjCtx
lngErr is not zero, we enter the section where we will investigate the problem further. Before doing that I will do a
SetAbort. It could be argued whether this is a good idea because perhaps we can do something to recover from the problem higher up in the call hierarchy. I agree that
SetAbort isn’t what should be used all the time, but most often I do it like this instead of using
DisableCommit. “Simple is beautiful”.
Error trapping is then deactivated so that we will pop out of this method when we raise a new error. After that the error is investigated. If we didn’t expect this error, a last resort would be to raise an unexpected error.
jnskErrRaise takes an enum for which error was raised (in this example the sample component lives in the layer I call Domain and that’s the reason for using “do” as the prefix to the new error code). I also send all other relevant information to the
jnskErrRaise sub so it can do an
Err.Raise and some other interesting stuff like logging the error etc.
jnskErrSave lngErr, strError, lngErl
When an error occurs in the regular code, I will go to the
ErrorHandler above. The first thing I do there is to call a sub (
jnskErrSave) that will save the error information in my local variables. That way you don’t risk the problematic effect of having the error object emptied. (The error object is still valid in
jnskErrSave since I don’t set up any error trapping in that sub.) Note that I will save the
Erl information to the local variable called
lngErl. This means that if you have numbered the lines of the code, you have caught information about the exact line where the error happened. That is really powerful, when it comes to trying to find an error.
After that I call
jnskStop which is only a sub, acting as a persistent breakpoint. I often find it interesting to get a halt in my code as close to the error as possible. If I have places in my code where I expect errors to happen, I don’t have the problem as with the regular “Break in Class Module” that can be set in the VB IDE, since using
jnskStop is configurable.
Finally I jump to the
ExitHandler (as you saw earlier) for further processing of the problem.
WAS THAT ALL THERE IS TO IT?
No, of course not! This is only the beginning. I was lazy and trapped only the unexpected case. I didn’t really do anything with the problem in this example. I only popped it. Some errors should of course be taken care of locally.
You also have to add more standard code, for example when it comes to trapping errors from ADO and the database. I haven’t shown many comments either, not even headers for the class and the method. You should of course add a lot of those. And more…
Do you think that there is more code here for error trapping than “real” code? This is often the case. But remember the parallel with re-papering. If you want to have a good end result, then the preparation will often take more time than the “real” work. You could of course also easily create an add-in for writing the skeleton for you.
Do you like to use the same code structure for components that shouldn’t run as configured components in COM+/MTS? I certainly do because of several reasons, but I will skip those arguments for now. To accomplish that, I’m using a trick by faking an object context interface, an object control interface and a conditional compilation argument that I have called MTS. (The faked interfaces are there only to make the code compilable. I could escape those faked interfaces by adding a lot more conditional compilation sections to my code, but I think this strategy gives much cleaner code since I don’t have any conditional compilation sections in the “real” code, only in my shared foundation routines.) There are of course other strategies that could be used too. You could for example check if
GetObjectContext() is returning
Nothing. You can also see if
ObjectControl_Activate was executed. This is actually yet another subject that needs an article of its own.
I also write my transactional code so that if your transactional component isn’t configured for COM+ transactions, it will use ADO transactions instead. Boy, do I have a lot of articles that I need to write or what?
Is it really important nowadays to pay attention to the code structure, the obscure error trapping stuff in VB6, and so on, when VB.NET is soon to arrive? Well, in my opinion the answer is “yes”. (You would have been surprised if I said “no” here, right?) First, VB.NET won’t be here soon and perhaps you would like to do some work now? I also believe that the best preparation for the future is to have a solid ground with consistent code, error trapping construction that maps the one in VB.NET and as much as possible in general and shared routines. All this should give as smooth a transition as possible when it’s time.
Are there any drawbacks with standardised code? Some developers argue that it will lead to a situation where developers stop thinking and don’t understand what’s going on. On the other hand, the code will be consistent and probably easier to understand. Of course, you must always understand the code, otherwise you can’t use it. It will blow up in your face one day.
I don’t use GlobalMultiUse classes for my jnsk-subs/functions. Instead I use plain old code modules. The reason for this is that I’ve had strange problems with GlobalMultiUse classes in configured components and when you have had a problem, you don’t forget it for a long time, right? The problem might have been solved ages ago, but better safe than sorry. (I’m not the only one to having had those problems, by the way. There are every now and then discussions about this in the COM+ newsgroups.)
There was an often-discussed problem in COM+ Component Services version 1, where the error object was emptied when the root object called a second object, the second object and root object both called
SetAbort. This problem is solved with Windows 2000 SP1 (and it wasn’t there in the first place in MTS). If you like to use this code structure in COM+ before the SP1, then you have to skip the
SetAbort in the secondary object. But do then remember that you shouldn’t use that object (at least not the same method), as a primary object.
In my latest article at VB2TheMax An error that must be trapped I said that if you disable JITA, you won’t get
Deactivate for the
ObjectControl-interface. This can be a reason for using
GetObjectContext() everywhere instead of keeping a module-global
mobjCtx as I do in my proposal above.
As usual with my articles, most of the stuff is also relevant if you use NT4 and MTS.
In this article I have shown you my ordinary code structure for COM+ components. Hopefully there was an idea or two for you to adopt. Next time I will discuss Assertions and hopefully give you a few new tips on how to use it, we’ll see.
Finally, perhaps you are wondering how the re-papering of the room went? Well, it’s finished any month now.