How Visual Basic COM+ Objects Work Internally
by Jose Mojica10/04/2000
Have you ever wondered what Visual Basic objects look like in memory? For most programmers, the answer is "no." However, you may have wondered, "If I create an object and place it in variable A, then add the object to a collection, and then set the variable A equal to Nothing, does that destroy the object in the collection as well?" In other words, can I do the following?
Dim Acct As CChecking
Set Acct = New CChecking
Dim Coll As Collection
Set Coll = New Collection
Coll.Add Acct
Set Acct = Nothing
This code is valid, but after adding the item to the collection, if I set the original value to Nothing, does the object inside the collection get destroyed as well?
I teach Essential COM+ for Visual Basic programmers with a company called DevelopMentor. Whenever I explain how to use the Implements statement to implement an interface, most people agree that it is a cool thing to do. Then I ask, "So how does it work? How is it that I am able to switch the interface in an object I am talking to?" More than that, "How is it that I am able to write a COM class in VB and request an interface in a C++ client program?" I always get blank stares, and the answer is often something like "It's magic!"The truth is that as Visual Basic programmers, we are spoiled. We have been fed this drug called Rapid Application Development (RAD) that says you do not have to worry about how it works, you simply have to know how to do it, and it all works "like magic." You may have a happy and healthy life without knowing how things work. The sad fact is, however, that sooner or later you'll be faced with a situation in VB that you cannot solve, and you'll spend weeks fooling with settings. Then, you'll end up with a set of rules like "Set this to this setting, and if it does not work, set it to this other setting."
The rules will work in 99% of the cases, but then when your software is out in the field you'll come up with an exception and your boss will ask you why it is not working. Your only answer will be something like "It should have worked." Your boss will ask you to explain to him or her how it works and all you'll be able to explain is the steps you have been taking blindly to make it work 99% of the time.
The best way to know the answers to the questions above is to understand how VB objects are actually put together in memory. But before we get to that, let's review the concept of interface-based programming.
Interface-based Programming
- Classes have state, behavior and identity.
- State has to do with the fact that each class may hold information that belongs to each instance of the class.
- Behavior refers to the functionality of the class. Instances of the same class will execute the same code.
- Identity has to do with mapping behavior to state.
If a class has a method called SayHello, when the OS loads the program, there is only one copy of the code for SayHello in memory. When a client creates two instances of the class and then calls the SayHello method, the compiler sends to the SayHello method the address in memory of where the state for that instance of the class lives. This address in memory translates to the "Me" object that you can use within a VB program. The way this mechanism works is similar to what you would have to do if you wrote a general function SayHello outside of the class in a module. If you would like the function to work for two different objects, you would have to pass the object as a parameter to the function. The compiler does the same thing when the SayHello method is part of a class--it passes the "Me" object as a hidden parameter to the function. This enables the function to work with any instance of the class.
There are two aspects to a class's behavior: interfaces and implementation. The interface of the class is the protocol that the client code (or the code using the class) uses to communicate with the class. The interface is simply the set of methods that the client is going to call and their signature. The term "signature" refers to both the input and output parameters for the methods in the class.
Imagine that you are building a banking application. After some design consideration, you find that the application will need at least two main classes: CChecking to represent a checking account and CSavings to represent a savings account. As the designer, you realize that certain methods, which exist in both classes (CChecking and CSavings), can have the same signature. For example, both the CChecking and CSavings classes may have a method called MakeDeposit that takes in a currency parameter to stand for the amount to deposit in the account, as follows:
Public Sub MakeDeposit(ByVal Amount As Currency)
End Sub
Both classes would most likely have the same method with the same signature. This is to our advantage because if we have the same protocol for communicating with both classes, it will simplify how we write our client code. In other words, a client may use essentially the same code to talk either with a CChecking class instance or a CSavings class instance.
Besides the interface, the class' behavior includes the implementation. The implementation is defined by the code in each method. It may be possible that even though the CChecking class and the CSavings class have the same method definition for MakeDeposit, that the code of the method in each would be different. In fact, that is very likely to happen. For example MakeDeposit in CChecking may not make the resulting balance available immediately, whereas money deposited into a savings account may make the funds available immediately.
Inheritance is the ability to write one class, and then build other classes on top of that. The parent class is known as the base class, the child class is known as the derived class.
Inheritance can be available in two flavors. Inheritance can refer to interface inheritance or it can refer to code inheritance. Both can be done in languages like C++. In Visual Basic 6, it is possible to do interface inheritance but not code inheritance. That is, we can define one class, and then tell VB that a second class will have the same set of methods and the same signature for each method as the base class. So in the case of the CChecking class and the CSavings class, we can define the methods in one of the two classes, then tell VB that the other class will have the same methods. Suppose we have the CChecking class as follows:
class CChecking
Public Sub MakeDeposit(ByVal Amount As Currency)
End Sub
Then we can build a CSavings class to have the same interface. To do so we use the Implements operator as follows:
class CSavings
Implements CChecking
Private Sub CChecking_MakeDeposit(ByVal Amount As Currency)
End Sub
As soon as you add the command Implements and specify the interface you wish to support, VB will add the object to the wizard bar object drop-down box. (The wizard bar is located on top of the code window.) You must select the CChecking interface from the drop-down. Doing so sets the right-hand side drop-down box in the wizard bar to display all the methods in the interface. When you use interface inheritance you are specifying that you are going to support all the methods in the interface. This means that you must add all the method definitions to your code, even if you choose not to write code for some of the methods.
The above code looks a little awkward because we are saying that the CChecking interface is supported in the CSavings class. Another way would have been to design the CSavings interface first and implement it in CChecking. However, a much better design would have been to create a neutral class definition that defined the methods the two classes would have in common. Therefore, we can create another class called IAccount. (We use the name IAccount instead of CAccount to denote an interface.) This class will provide the definition only of the methods that both classes will support. The IAccount class will not have any code of its own because it would not serve us any purpose-- since we cannot use code inheritance in VB 6, only interface inheritance. Thus we add a new class to our project, name it IAccount, and add code to it as follows:
class IAccount
Public Sub MakeDeposit(ByVal Amount As Currency)
End Sub
Then the CChecking class and the CSavings class would implement the IAccount interface as follows:
class CChecking
Implements IAccount
Private Sub IAccount_MakeDeposit(ByVal Amount As Currency)
End Subclass CSavings
Implements IAccount
Private Sub IAccount_MakeDeposit(ByVal Amount As Currency)
End Sub
Each class may also have Public methods of its own. For example, the CChecking class may have a method called OrderChecks, and the CSavings class may have a method called AddInterest. This would mean that the classes might have the following definition:
class CChecking
Implements IAccount
Private Sub IAccount_MakeDeposit(ByVal Amount As Currency)
End SubPublic Sub OrderChecks(ByVal Quantity As Integer, ByVal
StartingNumber As Integer)
End Subclass CSavings
Implements IAccount
Private Sub IAccount_MakeDeposit(ByVal Amount As Currency)
End SubPublic Sub AddInterest()
End Sub
This means that every class has at least one interface known as the default interface. The default interface is the set of methods that are marked as public in a class. In fact all the Implements operator does is to enable one class to adopt another class's default interface. In our previous example code, all three classes, IAccount, CChecking, and CSavings, all have a default interface. The default interface for IAccount has only one method, MakeDeposit. The CChecking class has one method in its default interface, OrderChecks, and the CSavings account class has one method in its default interface, AddInterest. The CChecking and the CSavings classes both adopt the default interface of the IAccount class.
VB has a mechanism by which we can specify which interface we would like to talk to for a certain class. If a class like CChecking implements a second interface like IAccount, you may inform VB if you wish to speak through the default interface for the class or through the IAccount interface. The mechanism is easy to use, you simply declare a variable for the type of interface you wish to use, then you create an instance of an object that implements the interface. The following code shows you how to create an instance of the CChecking class and use the IAccount interface instead of the class's default interface.
Dim Acct As IAccount
Set Acct = new CChecking
Call Acct.MakeDeposit(500)
Notice that you do not have to start with the IAccount interface. You could also specify that you wish to use the default interface, then switch and use the IAccount interface. For example you may do the following.
Dim Checking As CCheckingThis may seem a little strange at first glance because we started with a variable of type CChecking and then set it equal to a variable of type IAccount. Even more interesting than switching what interface we are talking to is the idea that we can create functions that work equally well for both the CChecking class and the CSavings class. One thing we can do is place instances of both classes in a collection as follows:
Set Checking = New CChecking
Dim Acct As IAccount
Set Acct = Checking
Call Acct.MakeDeposit(500)
Dim Accounts As Collection
Private Sub Form_Load()
Set Accounts = New CollectionDim Acct As IAccount
Set Acct = New CChecking
AddAccount Acct
Set Acct = New CSavings
AddAccount Acct
End SubPrivate Sub AddAccount(ByVal Acct As IAccount)
Accounts.Add Acct
End SubPrivate Sub AddMoneyToAccounts()
Dim Acct As IAccount
For Each Acct In Accounts
Call Acct.MakeDeposit(10) 'Christmas Bonus for Employees
Next
End Sub
Notice from the code above that there are two functions that use the IAccount interface instead of using each of the default interfaces. The AddAccount method receives a parameter of type IAccount. That means that we are able to send in any of the classes that implement the IAccount interface. It then places the instance of the object into a collection object. Later on we are able to navigate through the items of the collection in the method AddMoneyToAccounts (a very generous gesture from the bank). Notice that in AddMoneyToAccounts we are talking to each account via the IAccount set of methods because each item in the collection supports them.
How does it work?
Let's switch focus to how the technology actually works. First let's talk about how the compiler works. When you write a line of code like Call Acct.MakeDeposit(500), the compiler has to know where the code for that function is going to reside in memory. The code for the executable, or for a DLL, gets loaded at runtime into virtual memory. A call into a function is simply a change in the instruction execution pointer of one location in memory to another. In other words, your program is executing one line of code, and by using the Call instruction we are telling the OS to tell the CPU to jump to another memory location and start executing the code at that location. What this means is that at compile time VB needs to know where to jump in memory to execute the correct function.The location in memory for the function MakeDeposit is dependent on a number of factors, many of which exceed the scope of this article. The bottom line is that, without knowing where to jump to in memory, we cannot switch the instruction pointer in the CPU to a new location. This doesn't mean that we have to know the exact location in memory at the compile time; the compiler can add code when the image is compiled to calculate (at run time) the address where it needs to jump. In this case the compiler only needs to know what variables will be involved in the calculation. In the case of VB objects, the compiler (at runtime) builds a lookup table, one for each interface. The lookup table gets built at run time. It contains the memory addresses for the functions that constitute the interface.
All that the compiler needs to know for the client code at design time is two things: how to obtain the address where this lookup table is located in memory, and which function within the lookup table to call. In the case of the line Acct.MakeDeposit(500), the compiler writes in code at compile time to find out first where the lookup table for the IAccount interface resides in memory, then the offset of the MakeDeposit function within this lookup table. For example, take the following code:
Dim Acct As IAccount
Set Acct = New CChecking
Call Acct.MakeDeposit(500)
This code tells the compiler to first allocate memory for a container variable to hold the location of the lookup table for the functions of IAccount. This is the purpose of the line Dim Acct As IAccount. The compiled code will simply allocate some memory for the Acct variable to hold a pointer to a lookup table of type IAccount. The container holds an address of four bytes (or 32-bits which is the addressing mode of the W2K operating system).
The second line, Set Acct = New CChecking, essentially does two things: It tells the compiler to write code to allocate memory that represents the CChecking class, and then it tells the code to place in the Acct container the address to the lookup table. Which lookup table? VB interfaces are flexible enough so that you may have multiple classes using the same interface. In the example above, we had two classes using the IAccount interface: CChecking and CSavings. Both of these classes have a different implementation for the MakeDeposit function. This means that there are two lookup tables that look like IAccount: one that maps to the CChecking class and one that maps to the CSavings class. Thus, the line Set Acct = New CChecking tells VB to store the address in memory of the lookup table that maps to the CChecking implementation.
The last line, Acct.MakeDeposit(500), tells the compiler to jump to a particular offset within that lookup table. In this case MakeDeposit is the only function in the table; therefore, this instruction is simply to jump to the memory address specified in the first location in the lookup table. If we had two functions, say, MakeDeposit and GetBalance, and the client code called GetBalance, then the compiler would simply burn into the image to jump to the address specified in the second entry of the lookup table. (Technically, according to the COM rules, MakeDeposit cannot be the first entry in the table as you will see later in this article. But for the sake of the discussion let's assume that it is the first method in the lookup table.)
This mechanism is known as early binding or v-table binding. It is known as vtable binding for historical reasons. The mechanism that uses lookup tables in this fashion to resolve addresses to functions is heavily used in C++. The lookup tables are known as virtual function tables or v-tables. The address to a particular lookup table is known as a vptr (short for v-table pointer).
The beauty of this mechanism is that it can be implemented in any language. If a C++ client would like to speak to a VB object, all it needs to know is how to obtain a pointer to a lookup table then how to jump to a particular function within the lookup table. (It also has to know the calling convention of the function--the order in which the parameters are sent.) The C++ compiler is able to write this type of code for looking up functions from source code that uses C++ classes. In other words, a C++ developer will code their application as if they were using a pointer to a "pure virtual" class and the compiler will interpret the code to use the lookup mechanism described earlier.
This lookup mechanism is not exclusive to Visual Basic, it is actually the mechanism that COM objects use in order to communicate with one another. A pointer to a COM interface is simply a pointer to an address that points to a lookup table. In other words, a vptr to a vtable, illustrated in Figure 1.
Figure 1: A VB variable to an object stores a memory address (vptr) to a function table (vtable)
VB uses this mechanism extensively, to the point that almost every object in VB uses COM interfaces. In fact, VB client code that uses VB classes in a DLL has really no idea that it is communicating with a VB object, all that it knows is that it is communicating with a COM interface, and thus it always knows how to talk to it--by obtaining the pointer to the lookup table and making a call to a particular entry in the table.
Every VB class has at least one lookup table: the default vtable for the default interface. The default vtable has the entries for the methods that are marked as public in the class. In the case of CChecking, the compiler writes code to build a vtable of only one entry: the address to the OrderChecks function. When the memory for the CChecking class gets allocated, the top portion of the memory object is the address to the default vtable. Figure 2 shows you what the object looks like in memory after New is called to create it.
Figure 2: The memory allocated for the object contains a vptr to the default interface's vtable
Consider the following code:
Dim Checking As CChecking
Set Checking = New CChecking
If we were to declare a variable of the type CChecking and set it to the memory of the object, we would be telling the compiler to simply provide us with the address to the default lookup table.
Whenever we use the Implements operator, we are telling the compiler that the class is going to use a second vtable. In the case of the CChecking class we have written Implements IAccount. This means that the class uses two lookup tables (or vtables). This also means that there are two vptrs located within the memory for the CChecking class. In other words, the object knows of two addresses to lookup tables: one for the CChecking class and one for the CSavings class. The diagram in Figure 3 shows you the memory contents of the CChecking account class.
Figure 3: The memory map of a VB object when you use the Implements command
With this knowledge, consider then the following code:
Dim Checking As CChecking
Set Checking = New CChecking
Dim Acct As IAccount
Set Acct = Checking
This code first uses the address for the default vtable, then switches to use the IAccount lookup table. Now, although it is true that COM interfaces may look the same, it is not true that the object in memory has to look the same despite which language created it. In fact, Figure 4 shows you two possible representations in memory for two COM objects.
Figure 4: Comparing the memory layout of a VB COM object to a C++ COM object
On the left is the memory layout of a VB object, on the right is the memory layout of a C++ object that uses multiple inheritances to support COM interfaces. COM was required to enable different object compilers to produce different memory layouts for objects internally. However, notice that in both cases the objects have vptrs to vtables in memory. It is only the location of these vptrs that is different. (In fact there is no rule that says that there has to be two vptrs for two different interfaces--but let's not go there yet).
Because the layouts in memory are different from compiler to compiler, COM provides a way for each object to provide the client with the desired address to a lookup table. All that the client code cares about is obtaining the address to the lookup table. Microsoft has defined one interface that serves as the starting point for every other interface: It is called IUnknown. The principle is this: Every object needs to support at least one vtable, known as IUnknown. What's more, every vtable must have the methods of IUnknown as the first three entries. The IUnknown interface has three methods:
class IUnknown
Public Function QueryInterface(ByVal InterfaceID As IID) As Long
Public Function AddRef() As Long
Public Function Release() As Long
end class
This code is given in pseudo VB code in order to make it easier to talk about. In reality the interface is defined in IDL (the interface definition language) that uses a C++-like syntax. Ordinarily, the interface methods cannot be called in VB directly. (But for work-arounds that allow you to directly call the methods of IUnknown, see Visual Basic Shell Programming, written by J.P. Hamilton and published by O'Reilly.) Nonetheless, the method names are accurate, and the parameters are close to what they would look like in VB if you could use it. (For the actual parameters, consult MSDN. See the help page entitled IUnknown::QueryInterface in the Platform SDK:COM documentation for details.).
The heart of the IUnknown interface is the QueryInterface function. Through the QueryInterface function, a client can ask the object to provide it with a particular vtable. The function has one in parameter that stands for the interface ID. As a developer of COM interfaces it is your job to assign each interface a unique number known as a GUID (or globally unique identifier). A GUID is a 128-bit number that is unique in both space and time. You do not have to come up with the number yourself. Microsoft provides a tool called GuidGen that generates these numbers. Whenever we use a GUID to refer to an interface, we call the GUID an interface ID, or IID for short. Thus, the QueryInterface function takes in an IID and returns the address to the particular lookup table that represents the requested IID. The most basic rule of COM says that every lookup table must have the three methods of IUnknown first. (We will discuss the other two shortly.) This means that the two vtables for the CChecking account contain the methods for IUnknown first, then the methods for each particular interface as illustrated in Figure 5.
Figure 5: Every COM interface is derived from IUnknown. This means that the first three methods in every vtable are the methods of IUnknown.
Why does every vtable need to have these three methods? By enforcing this rule, a client may navigate to any lookup table from any starting point. In other words, suppose that I have the following code:
Dim Checking As CChecking
Set Checking = New CChecking
Call Checking.OrderChecks(100,132)
Dim Acct As IAccount
Set Acct = Checking
Call Acct.MakeDeposit(500)
Dim Checking2 As CChecking
Set Checking2 = Acct
The code begins by using the default lookup table, then needs to switch to using the IAccount lookup table (or vtable). The line that reads Set Acct = Checking basically calls the QueryInterface function in the default vtable. The resulting vptr is stored in the Acct variable. I can then use that lookup table to switch to any other interface. In the code above I have a second variable to store the default vtable, Checking2. Which interface can I use to switch to the default interface? I can use any other interface, because they all have the QueryInterface method first. In VB you never have to see the QueryInterface method call. However each time you change the interface you are talking to, you are basically telling VB to call QueryInterface on the particular interface to obtain a second vptr.
What do the other two methods in the IUnknown interface do? AddRef and Release are for reference counting. What is reference counting and why do we need it? All general-purpose COM objects (I say general-purpose because there are exceptions.) have an internal reference counter. The idea is that each COM object should keep track of how many variables in the client code are referring to the same object. If no variables are using the object, then the object should free itself from memory. A client is able to tell the object how many variables are using the object by calling AddRef on the object for each variable that is using it. The creator of the object would write code for AddRef to simply increment an internal counter. Normally this counter is stored in a member variable. Whenever the client doesn't need a variable to point to the object, it calls Release. Release brings the counter down by one. The counter reaching zero tells the object to release itself from memory. There is one more little rule: whenever a client calls QueryInterface, the object should automatically call AddRef to increase the counter. The following two code examples will show you how reference counting works.
Private Sub Comman1_Click()
Dim Checking1 As CChecking
Dim Checking2 As CChecking
Set Checking1 = New CChecking
Set Checking2 = Checking1
Set Checking1 = Nothing
End Sub
In this code we have two variables that are using the default interface. In this case we do not want to switch the interface we are talking to, we simply want two variables to point to the same object. In VB, whenever we have two variables pointing to an object as in the example code above, VB does not copy the memory contents for the object for the second variable, so that there would be two objects in memory. What VB does is simply increment an internal reference counter so that both variables will point to the exact same object in memory. In fact, you can verify this by using two VB functions: VarPtr, and ObjPtr. VarPtr will return the location in memory of a variable. ObjPtr will return the location in memory for the object the variable is pointing to. You can verify that both variables, although they are in different locations in memory, point to the same instance of the CChecking class. Note the following code:
Private Sub Comman1_Click()
Dim Checking1 As CChecking
Dim Checking2 As CChecking
Set Checking1 = New CChecking
Set Checking2 = Checking1
MsgBox "Are the two variables the same address in memory?"
& VarPtr(Checking1) = VarPtr(Checking2)
MsgBox "Are they pointing to the same object in memory?"
& ObjPtr(Checking1) = ObjPtr(Checking2)
End Sub
Because both variables are using the same object we need to increase the counter to two to let the object know that there are two references to it. If you set a variable equal to Nothing, we are notifying the object that we no longer need a reference to it, and in that case, it should decrease the counter by one. Therefore, whenever we use variables of the same type, the VB compiler adds code to simply call AddRef. Whenever we set a variable equal to Nothing, the VB compiler adds code to call Release. There is another instance in which the compiler needs to call Release in the above code--when a variable goes out of scope. In other words if we forget to set a variable equal to Nothing, as in the case of the Checking2 variable in the code above, then the compiler automatically calls Release whenever the variable goes out of scope. Earlier I said that the object needs to increase the counter after each call to QueryInterface. Why is that? The answer is simple, once you understand reference counting. Consider the following code:
Dim Checking As CChecking
Set Checking = New CChecking
Call Checking.OrderChecks(100,132)
Dim Acct As IAccount
Set Acct = Checking
Call Acct.MakeDeposit(500)
I made the statement that the line that reads Set Acct = Checking internally calls QueryInterface. After the line completes, however, how many variables are referring to the same object? There are two: Checking and Acct. Notice that the variables refer to the same object, but to different vptrs to different vtables. Just because we switched the interfaces does not mean we lose the reference to the object that is stored in the Checking variable. Thus, because each time we switch interfaces there are multiple variables using the object, the rules say that each call to QueryInterface should call AddRef internally.
So, when we started this journey into how VB objects work internally, I posed a question. I asked "If I create an object using variable A then add it to a collection, can I then set the variable A to Nothing?" The answer is YES, because when we add the object to the collection, all we do is increase the internal reference counter by one. Therefore, by adding the object, we add one to the counter--making the reference count equals to two. When we set the A variable to Nothing all we do is decrease the reference count by one. Another question: What happens when I use the following code?
Dim Checking As CChecking
Set Checking = New CChecking
Call GiveBonuses(Checking)Private Sub GiveBonuses(ByVal Acct As IAccount)
Call Acct.MakeDeposit(10)
End Sub
In that case, I begin with a reference to the CChecking default interface. When I pass the variable to the function Acct, VB calls QueryInterface and passes the resulting vptr to the GiveBonuses function. At that point there are two references to the object. When the GiveBonuses procedure finishes, the local variable Acct goes out of scope, which brings the count down to one. When the Checking variable goes out of scope, the reference counter will go down to zero, which will cause the object to get destroyed in memory.
Completing the Memory Map and QueryInterface Optimizations
Figure 6 shows you a more complete view of a VB COM object in memory. Notice that the memory map shows the reference counter. In fact, the reference counter for each object lives in memory exactly 32 bytes away from the vptr to the default interface. There is one more object represented in the memory map that has not been discussed. I refer to this object as the IUnknown manager. This little object serves to bring the blob of memory together as one unit.
Figure 6: A more complete view of a VB COM object. VB COM objects have an IUnknown manager object that provides a centralized implementation for the methods of IUnknown.
As I mentioned before, each vtable contains the methods of IUnknown as the first three methods. However, whenever they receive a call, VB has a little object called the IUnknown manager that handles the IUnknown requests for all the vtables in each object. This object also has its own vtable--this vtable contains only the methods for IUnknown. Figure 6 illustrates the forwarding mechanism. The idea is that any requests to QueryInterface get forwarded to the vtable for IUnknown, which is implemented by the IUnknown manager. It is the manager's responsibility to hand out the requested vtable. It is also this manager object that is in charge of increasing and decreasing the counter for the entire object regardless of which interface the request comes in through.
Shameless Plug
It is impossible to explain all the details of the how VB COM objects work in this article. In fact, the information in this article only explains how certain clients work. There are also scripting clients, like IE, that cannot really talk to just any interface. These clients know how to bind to only one interface: IDispatch. The truth is that to provide support for these clients all VB-made interfaces are derived from IDispatch rather than from IUnknown directly (IDispatch is derived from IUnknown). This means that to make a truly complete picture of VB interface vtables we would have to list not only the methods of IUnknown but also the methods of the IDispatch interface. To get more information about how VB objects support scripting clients, how it is that VB allocates memory for each object, how it is that a VB client can talk to a COM object remotely, how the new COM+ services work, and more, read my upcoming book COM+ Programming with Visual Basic, which is scheduled to be published by O'Reilly early next year.
Jose Mojica is an instructor and researcher at
DevelopMentor. He
teaches courses focusing on enterprise development in COM+, IIS, and
Visual Basic. Jose is the author of COM+ Programming with Visual
Basic, an O'Reilly book scheduled to release early next year.
When he is not writing or teaching he enjoys playing video games
with his wife and two sons in his house in Florida.
![]()







