Visual Basic makes some things incredibly easy. Even a four-year-old can build an ActiveX DLL or an ActiveX EXE in the Visual Basic IDE using the Make command on the File menu. This command opens a dialog box with various options and an OK button. You simply click OK to build the current project into a binary server.
A few important things happen behind the scenes when you do this. First, Visual Basic automatically publishes information in a type library and bundles it into your server's binary image. The type library is important for marshaling purposes as well as for use by other development tools that need to create vTable bindings for your objects at compile time. Second, Visual Basic adds the code to your server to support self-registration. Finally, Visual Basic adds a class factory for each creatable class and adds support to interact with the SCM during object activation.
When you create a new server, you should open the Project Properties dialog box and change the Project Name and the Project Description, as shown in Figure 5-1. These important pieces of information are written into your type library. The Project Name creates a namespace for the coclasses within it. Clients can refer to coclasses in your server with a fully qualified name such as DogServer.CBeagle. The Project Description becomes the description of the type library itself. Other Visual Basic programmers see this when they add your server to their projects using the References dialog box. If you don't include a description, Visual Basic uses the Project Name as the description.
Figure 5-1. In the Project Properties dialog box, you can assign a Project Name and a Project Description to your server. These settings become top-level attributes of the server's type library.
As you know, the type library is bundled into the server's binary image. With an out-of-process server, this information is essential for marshaling purposes. With a local server, the universal marshaler can simply use the type library bundled into your server file for both the proxy and the stub. Since a remote server lives on a different machine than the client, the client can't take advantage of the server's binary image, which holds the type library. So how does the universal marshaler on the client machine build the proxy? You could copy the server file to each user's desktop machine, but that wouldn't make sense. The server file includes all of the coclass implementations, and the client machines need only the interface definitions.
Visual Basic lets you create a stand-alone type library by selecting the Remote Server Files option on the Component tab of the Project Properties dialog box. When you select this option for an ActiveX EXE or an ActiveX DLL project, the Make command creates a separate type library file with the .tlb extension. This file can be distributed to client desktops in a distributed environment so that the universal marshaler can build proxies as needed.
The Registry is a systemwide hierarchical database that is used to bind key names to numeric, string, or binary values. It was originally created to hold information about COM and OLE in the early days of 16-bit Microsoft Windows, but now it is used to store many other types of persistent configuration data. The Registry has top-level keys called hives. (A bonus fun fact: The term hive was coined as a clever play on words because the Registry uses a "B-tree" structure.) The SCM is entirely dependent on information stored in the Registry.
Every loadable coclass associated with an in-process server or a local server must have a Registry entry that associates it with a physical path to a server's binary image. Every remotable interface must have an associated path to a type library. Even the type library itself must be registered. As you will see, the SCM, the universal marshaler, and development environments such as Visual Basic need this information to do their jobs.
In Windows NT 4, the Registry key HKEY_CLASSES_ROOT\CLSID lists all implementations available to client applications on the machine. Each CLSID key contains one or more subkeys that indicate the location of the coclass, as shown in Figure 5-2. DLLs require an InprocServer32 subkey, while out-of-process servers require a LocalServer32 subkey. These keys hold the physical path to the server; the SCM uses them to find or load the server during object activation.
Any object that will be bound to a client with a proxy/stub pair requires additional Registry entries that allow the universal marshaler to locate the type library at run time. As you will recall from Chapter 3, a proxy/stub pair is specific to an IID. The universal marshaler generates a proxy and a stub by examining interface definitions from a type library. The Registry key HKEY_CLASSES_ROOT\Interfaces lists each interface along with the GUID of the type library where the interface is defined. The Registry key HKEY_CLASSES_ROOT\TypeLib tracks the physical location of every type library on the local machine. All of this information must also be written to the Registry before the server will work correctly.
Figure 5-2. Every creatable coclass must have an associated Registry entry to map the CLSID to the server's location. The SCM uses this mapping information to find and load a server during an activation request.
All modern COM servers are self-registering. That means that a server, when asked politely, should write all of its required information into the local computer's Registry. In the early days of COM, C++ programmers had to write registration code by hand against the Win32 API. Today C++ frameworks such as ATL and Microsoft Foundation Classes (MFC) can provide boilerplate code that handles these grungy registration details. Fortunately, Visual Basic automatically generates self-registration code when you build a server. A Visual Basic server registers all of the information required for both in-process servers and local servers. Remote servers that are accessed by clients from across the network add more complexity to the registration process and require extra attention. Chapter 8 covers this topic in greater depth.
When you build a server, the Visual Basic IDE automatically registers it on your development workstation. This makes it easy to build and test a client application after you build the server. Keep in mind that it's one thing to configure a server on a development workstation and another to register it on a user's desktop. Both are equally important for your server to run correctly.
Out-of-process servers register themselves each time they are launched. If you double-click on a server, it launches and registers itself. Once the server process is running, it realizes that it has no client and unloads. However, the polite way to register and unregister an EXE-based server is to use a standard command-line switch, as follows:
DogServer.EXE /RegServer DogServer.EXE /UnregServer
DLL-based servers are passive and can't register or unregister themselves. You register and unregister them using the Win32 SDK utility REGSVR32.EXE. You can run this utility from the command line by passing the path to the DLL with the appropriate switch, like this:
REGSVR32.EXE DogServer.DLL REGSVR32.EXE /u DogServer.DLL
As you can see, most of the details concerning the Registry and server registration are tedious. As a COM developer, you're expected to loathe all matters concerning component registration. If you're lucky, the responsibility for registering your servers will fall to another individual who's creating the application's setup program. Unfortunately, registration is one of the most painful and expensive aspects of deploying COM-based technologies. Fortunately, Windows NT 5 and COM+ will offer features to make server registration easier and more foolproof.
As you read in Chapter 3, typical COM clients create objects by passing a CLSID to the SCM. When you compile a Visual Basic client application against a type library, the CLSIDs are embedded into the client executable. Your calls to the New operator are translated into a call to the COM library that passes the CLSID and the desired IID to the SCM:
Dim Dog As DogServer.IDog Set Dog = New DogServer.CBeagle
The SCM calls your server and negotiates the creation and activation of a new object. If the object is created in an in-process server, the client is directly bound to the vTable for the requested interface. If the object is created in an out-of-process server, a proxy/stub pair is introduced between the client and the object during the binding process.
In one special case, calling the New operator doesn't result in a call to the SCM. When you create an object with the New operator using a class name that exists in the same project, Visual Basic can create and bind the object without the assistance of the SCM. This is a subtle but important point. There are times when you want the SCM to be involved in object activation, so using the New operator isn't an option.
Visual Basic 3 was the first version of Visual Basic that allowed programmers to activate COM objects using the CreateObject function. When you call CreateObject, you must always pass a ProgID. A ProgID is simply a text alias for a CLSID that identifies a coclass by qualifying both the project name and the class name, like this:
Dim Dog As Object Set Dog = CreateObject("DogServer.CBeagle")
You don't have to use ProgIDs or the CreateObject function in Visual Basic when you compile a client application against a server with a type library. CreateObject takes longer than a call to the New operator because it requires a call to the SCM just to resolve a ProgID to a CLSID. Once an application has the CLSID, the activation sequence is the same as in a call to New that uses the SCM. In the days of Visual Basic 3, CreateObject was always used with the Object data type. If you ever run across an outdated server that serves up IDispatch-only objects and doesn't have a type library, you must resort to using CreateObject and the Object data type.
Many programmers don't understand that you can use CreateObject to bind your client application directly to an interface's vTable. This technique isn't common, but it lets you wait until run time to decide what type of object to create. As long as you don't use references of type Object, you get the efficiency of custom vTable binding. You can use this technique in complex designs that exploit the polymorphic nature of COM and interface-based programming. Look at this example:
Function GetDog(ByVal Breed As String) As IDog Dim ProgID As String ProgID = "DogServer." & Breed Set GetDog = CreateObject(ProgID) End Function
This example shows an advanced technique for achieving polymorphic behavior. The CLSID information for creating a dog object doesn't even have to exist in the client application. The string value for the breed can be read from the Registry or a database; the client code that uses an IDog reference still uses vTable binding.
Such techniques are not essential, but it's good to know that they are available. ProgIDs are used primarily to support automation clients. Today the MTS run-time environment also relies on ProgIDs for object creation. Fortunately, every ProgID in your server will be properly written to the Registry by the self-registration code that is built into your server. Just remember that the value you set for the Project Name on the project's Properties tab is used as the first part of the ProgID.