In Visual Basic, building a COM-compatible object is deceptively easy: given the class definition for this object, you simply add the class to a particular type of VB project, make the project, and you're done. That's it, end of tutorial! In reality of course, there's more to VB COM than just doing make.
First, let's define some standard COM terminology. A coclass refers to a COM class, i.e. a class that is compatible with COM. What makes VB so powerful is that all classes created in VB are COM-compatible. It will be helpful if you also think of a coclass as denoting a concrete class, a class that contains functionality / code. A COM component is synonymous with coclass.
A COM server is one or more coclasses compiled into a single, distributable file. COM servers typically have the file extension .dll or .exe. A COM server must be registered on a client's machine before that client can instantiate any of the coclasses in the server.
There are two types of COM servers, in-process and out-of-process. If a client instantiates a coclass from an in-process COM server, the resulting object lives in the same address space (and process) as the client. However, if a client instantiates a coclass from an out-of-process COM server, the resulting object will live and run in a separate process --- on the same machine (local) or a different machine (remote). To build an in-process COM server in VB, you create an ActiveX DLL project, add one or more class modules, and perform make; the result is a file with the extension .dll. To build an out-of-process COM server, use an ActiveX EXE project type; make will then yield a file with the extension .exe.
|In-Process VB COM Servers|
The extension DLL stands for Dynamic Link Library, implying that an in-process COM server is linked into the client upon the first instantiation of any of its coclasses. Note that the entire COM server is linked in, not just the coclasses being instantiated. For example, suppose a COM server has 3 coclasses, and a client instantiates coclass1 and coclass2. Here's the resulting situation in memory on that machine:
Now suppose the user, on this same machine, starts up another process called Client #2 that instantiates coclass3. Conceptually, the situation in memory is now:
In essence, each process gets its own copy of COM Server. Why is this important? Because if COM Server contains "global" variables, it is important to note that these variables are not shared by the different client processes.
|Out-of-Process VB COM Servers|
As we all know, the extension EXE stands for executable program. Thus, out-of-process COM servers are completely separate processes, running asynchronously until a client makes a method call into one of the server's objects (at which point the client object blocks until the server object returns from the method call). For example, suppose we have an out-of-process COM server with 3 coclasses, and a client object on the same machine instantiates coclass2. The situation in memory is:
What if multiple clients instantiate coclasses from the same COM server? Typically, each instantiation yields a different object within the same server process:
However, whether these server objects run concurrently --- i.e. whether method calls from different clients execute at the same time --- depends on how the COM server is configured; we'll save this topic for later, when we discuss COM activation.
Note that the configuration of the COM server controls other things as well. In particular, whether or not: (1) the different server objects in the above picture can share "global" variables, and (2) instantiation yields multiple objects in the same server (as shown above) or an entirely new server process each time (thus # of processes = # of server objects).
|LAB: Building VB COM Servers|
Time for a lab exercise! The goal is to take an existing application, remove one of its classes, turn that class into a COM component, and produce a new, COM-based application. First we'll build an ActiveX DLL COM server, then repeat the exercise by building an ActiveX EXE COM server. The application simulates a long-running operation, displaying a progress indicator as it proceeds. Here's a picture of the app, which you can see this for yourself by running /VBCOM/Labs/COM Servers/App.exe (if you have not already done so, you can download the labs from the Setup page):
It is the progress indicator, defined by the class CProgress in /VBCOM/Labs/COM Servers/App.vbp, that will become a COM component.
- Startup VB, and create a new ActiveX DLL project. VB will define an initial class module called Class1. Select this class, and remove it from the project (Project >> Remove); when prompted, do not save changes.
- Now add the existing class module file "CProgress.cls" to the project: Project >> Add Class Module, Existing tab, navigate to /VBCOM/Labs/COM Servers/CProgress.cls, and open. Likewise, add the existing form "frmProgress.frm" to the project.
- Take a moment and review the code in CProgress. For help understanding this code, jump here.
- View the project explorer (View >> Project Explorer), expand the Class Modules, and select CProgress. View the properties window (F4), and change the Instancing property from Private to MultiUse. This makes the COM coclass publicly accessible to clients.
- Modify the project's properties (a very important step when building a COM server) via Project >> Properties. Under the General tab, set the name to "ProgressDLL" and the description to "_Progress Indicator In-Process Server (VBCOM)". Click OK.
- Finally, make your COM server via File >> Make, into the same directory as the other files. Save your work, and exit VB.
Congratulations, you have just built your first COM server! Your COM server resides in the file "ProgressDLL.dll" that you made; if you cannot see this file in your lab directory, make sure your explorer window is set to display hidden files. Now, to test your work, let's build a client application.
- Startup VB, and create a new Standard EXE project. Remove the initial form module Form1, and add the existing form /VBCOM/Labs/COM Servers/frmMain.frm.
- Modify the project's properties: set the startup object to be frmMain, and the name to "Client". Click OK.
- View the form, in particular the code behind the cmdProgress command button. The sub creates an instance of CProgress, shows progress in increments of 10, and then destroys the instance:
|Dim i As Integer
Dim j As Variant
Dim progress As CProgress
|Set progress = New CProgress||'** instantiate COM component|
|progress.Show||'** tell component to show itself|
|For i = 1 To 10||'** update progress|
|progress.Value = progress.Value + 10|
|For j = 1 To 1000000: Next j||'** pause to simulate operation|
|Set progress = Nothing||'** destroy COM component|
- Now run the application (F5). Depending on how VB is configured on your machine, you will either get an immediate compiler error ("User-defined type not defined", with CProgress highlighted), or the app will startup and show the main form. In the latter case, clicking the button will then yield the same error message: "User-defined type not defined".
- The problem is that our new client application is unable to locate the class CProgress. Whenever you want to use a COM component, you must first reference it so that VB can find it. Thus, view the list of registered COM components on your machine (Project >> References), and select the progress indicator component we built earlier --- i.e. check the item "_Progress Indicator In-Process Server (VBCOM)". Click OK.
- Now re-run the application. It should work perfectly, merrily showing progress in increments of 10!
- Finally, make an executable "Client.exe", save your work, and exit VB.
You should be able to run "Client.exe" outside of VB, and it should run identically to "App.exe". However, the former uses COM, and the later does not. If you want to run multiple clients and view multiple progress indicators on the screen, you will need to add a call to DoEvents inside the client's For-Next loop, and perhaps pause a bit longer as well.
At this point, it is very important to observe the following: no special programming was required to use COM with VB! The only differences from the original "App.exe" were configuration-oriented --- the use of a different project type in the server and setting a project reference in the client. On the surface, COM programming in VB is that easy.
For completeness, let's build the progress indicator into an out-of-process COM server. Once again, the only changes will be the use of a different project type for the server, and a different project reference in the client:
- Repeat steps 1 .. 6 above, except create an ActiveX EXE project, set the project name "ProgressEXE" and the description to "_Progress Indicator Out-of-Process Server (VBCOM)". After you make your project, you should end up with a COM server in the file "ProgressEXE.exe".
- Reopen the "Client.vbp" project, and under project references uncheck the in-process COM server and check your new "_Progress Indicator Out-of-Process Server (VBCOM)". Now run your client (F5), and it should function as before!
- To convince yourself that the COM server actually runs as a separate executable, insert a Stop statement in the client immediately after the instantiation of CProgress:
|Set progress = New CProgress||'** instantiate COM component|
|progress.Show||'** tell COM component to show itself|
- Now run the client from inside VB, and click the command button to execute the above code. VB should enter break mode with the
- If you want to play... To keep the COM server running, we need to keep at least one object alive in the server. This means we need to keep the object's reference count > 0. One way to do this in our clients is to keep object references in global variables, which never go out of scope. In the client app, first move the declaration of the progress variable from the Click event to the Declarations section (top) of the main form. Then, instead of instantiating CProgress in the Click event, now do this in the form's Load event. Finally, move the "Set progress = Nothing " statement from the Click event to the form's Unload event. Now when you run the client, the COM server will be started as well, and this same process will continue to run until the client terminates. Confirm this behavior using the Task Manager.
That's enough lab work for now, good job!
|Registering a VB COM Server|
As mentioned in the introduction, a COM server must be registered before a client can instantiate any of the coclasses in that server. However, in the above lab the client app (e.g. "Client.exe") ran just fine without any kind of explicit "registration" step. How can this be? Who registered the progress indicator COM server? And what does it mean for a COM component to be registered?
In short, registering a COM server means updating the Windows registry database on that computer. The registry enables COM to locate coclasses in response to client instantiation requests. To ease component development, VB automatically registers --- on your development machine --- any COM server you make (i.e. File >> Make). Thus, each time you do make, VB unregisters the previous version (if any) and registers the new version.
As evidence, let's break the "Client.exe" application by simply unregistering the COM server on which it depends. First, in preparation, locate the file /VBCOM/Misc/regsvr.reg and double-click to execute; this file adds some entries to your registry database that makes server registration and unregistration easier. Next, run your "Client.exe" in the lab directory /VBCOM/Labs/COM Servers/ and convince yourself that it shows progress without error. Now right-click on the file "ProgressDLL.dll" --- the in-process progress indicator COM server --- and select "Unregister COM Server" from the pop-up menu. Finally, re-run "Client.exe" and click the command button to show progress: this should yield "Run-time error 429: ActiveX component can't create object." In other words, the requested COM server is not registered on this machine. [ Note: if you did not receive an error, perhaps your client is instantiating the out-of-process version of the progress indicator. Unregister "ProgressEXE.exe" and try again. ] To fix the application, simply re-register the COM server by right-clicking on the appropriate file.
How can you find out what COM servers are registered on your machine? One way is to view project references. Startup VB, select Project >> References, and you should see something similar to:
This list is produced by VB using information culled from the machine's registry database; recall we used this dialog in the lab to configure the client.
COM servers can also be registered via the command-line. To register / unregister an in-process COM server, use the REGSVR32 utility:
|REGSVR32 /u ProgressDLL.dll|
In the case of out-of-process COM servers, run the server itself:
These approaches make it easy to automate the registration process, in particular during application installation on a client machine. This task is typically performed by the install program itself.
For now, we can safely ignore the registration problem since VB will automatically register COM servers for us. However, you need to keep this in mind when it comes time for deployment. We will revisit this issue in more detail when we present COM activation.
|In-Process vs. Out-of-Process?|
When developing a COM component, you are faced with an immediate decision: create an in-process server, or an out-of-process server? The short answer is that in-process COM servers offer better performance while out-of-process COM servers offer greater flexibility. The former are more efficient since the server objects live in the same address space as the client, enabling a much cheaper communication mechanism (a simple subroutine call). The latter are more flexible since they can be deployed remotely on server machines, without having to recompile any of the code.
At first glance, it would appear that multi-tier applications are best designed using out-of-process COM servers. However, it is becoming quite common to develop in-process COM servers and then deploy these components using surrogate processes. A surrogate process acts as a host for an in-process COM server, allowing it to run in an address space separate from the client --- just like an out-of-process COM server. However, the advantage is that the surrogate can provide services to its server objects, such as support for sharing state (i.e. "global variables"). The most important example of a surrogate process is Microsoft Transaction Server (MTS), which among other things provides support for distributed transactions. Thus, if you want to build COM components that work with MTS, you must create in-process COM servers.
What's next? A discussion of client-side COM programming.