Loading Component Code Dynamically

[Previous] [Next]

What do you know about COM so far? You know that a client must communicate with an object through an interface and that a client binds to an object at runtime and invokes methods through direct vTable binding. You also know that Visual Basic clients learn about components and interfaces by examining type libraries at compile time. These rules are the foundation on which COM is built.

But think about the following questions: How does a client create and bind to an object if it only knows two GUIDs, the IID and the CLSID? How does a client obtain the first interface reference to an object? COM provides an infrastructure that helps the client. However, the client must know how to call into the system to ask for assistance.

COM exposes a library of system-level functions that are collectively known as the COM library. This library is exposed through a set of DLLs that are installed on any COM-enabled operating system. The COM library is part of Windows 2000, Windows NT 4, Windows 98, and Windows 95. (But the runtime files in future versions of COM+ will probably be decoupled from the operating system.)

Client applications written in C++ typically interact with the COM library through calls to OLE32.DLL. Visual Basic programmers are shielded from this DLL by their runtime layer. Figure 3-3 shows the differences in the layers between a COM application written in C++ and one written in Visual Basic.

Client applications must call upon the services provided by the COM library to create and connect to objects. The sequence of object creation must be carefully orchestrated because a client must bind to an interface, not to a class. This leads to a catch-22: How can a client create an object when it can never see the definition of the creatable class? The following sections describe exactly how COM makes this possible.

Figure 3-3 C++ programmers talk to the COM library directly. Visual Basic programmers are shielded from this library by the Visual Basic runtime layer.

Object Activation

A client application can discover the CLSID of a coclass as well as which IIDs it supports at compile time through inspection of a type library. However, COM requires that no other dependencies be built between clients and components. As long as a client knows which CSLID and IID to use, it can create and bind to an object. Once the binding has taken place, the client can invoke methods on the object by accessing the vTable associated with the IID. The act of creating and binding to an object is called activation. When a client wants to activate an object, it must make a call to the COM library and pass a CLSID and an IID.

Activation support must be built into COM's infrastructure because clients must always be shielded from the definitions of concrete classes. This is a fundamental principle of interface-based programming. After all, if a client could see the definition behind a component, it could generate a dependency on the object's data layout at compile time. Instead, COM puts the responsibility of allocating memory and creating the object on the server. The infrastructure support supplied by COM simply plays the role of middleman. It takes an activation request from the client and forwards it to the server. The server is the one calling New on the class when the object is instantiated.

The COM service that helps clients with activation is known as the Service Control Manager (SCM). It's affectionately called "the scum" by COM programmers who are in the know. During in-process activation, a session of the SCM is loaded into the client process from OLE32.DLL. To assist with out-of-process activation in Windows 2000, a session of the SCM is loaded from RPCSS.DLL into an instance of SVCHOST.EXE, as shown in Figure 3-3. Note that in Windows NT, the SCM is a service that runs as RPCSS.EXE.

A client application always interacts with the SCM through OLE32.DLL. C++ programmers activate objects by directly calling a function named CoCreateInstance (or a related function named CoCreateInstanceEx). Visual Basic programmers can activate objects by using the New operator or the CreateObject function. When a client application calls New or CreateObject on a component that resides in a COM server, Visual Basic has enough information to determine the associated CLSID and call CoCreateInstance on its behalf.

For example, when a client application makes a call to the New operator on a component in an ActiveX DLL, the Visual Basic compiler embeds the component's CLSID in the client's executable image. At runtime, the call to New is translated by the Visual Basic runtime into a call to CoCreateInstance.

When the Visual Basic runtime passes the CLSID to CoCreateInstance, the SCM uses configuration information in the Windows Registry to locate the server's binary image. This is typically a DLL or an EXE. This means that a COM server requires an associated set of Registry entries, including a physical path to its location. Each COM server is responsible for adding its configuration information to the Registry when asked.

CLSIDs and the Windows Registry

The SCM is entirely dependent on information stored in the Registry. Every loadable component associated with an in-process server or an out-of-process server must have a Registry entry that associates it with a physical path to a server's binary image.

The Registry key HKEY_CLASSES_ROOT\CLSID lists the components available to client applications on the machine. Each CLSID key contains one or more subkeys that hold configuration data about the associated component, as shown in Figure 3-4. Components in an in-process server such as an ActiveX DLL require an InprocServer32 subkey, while components in an out-of-process server such as an ActiveX EXE require a LocalServer32 subkey. These keys hold the physical path to the server file; the SCM uses them to find and load the servers during object activation.

click to view at full size.

Figure 3-4 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.

There are other important Registry entries in addition to those for the CLSID. The Registry key HKEY_CLASSES_ROOT\TypeLib tracks the physical location of every registered type library on the local machine. By the way, when you bring up the References dialog box, Visual Basic examines this key to fill in the list of available references.

As you'll see later in this chapter, each IID plays an important role in binding an object to a client that's running in another process. Every interface that is remotable must have associated configuration data in the Registry key HKEY_CLASSES_ROOT\Interface. This configuration data makes it possible to make connections that span process and machine boundaries. You'll see more specifics about this later in the chapter.

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. Fortunately, Visual Basic automatically generates self-registration code when you build a server. A Visual Basic server is capable of registering all the information about its CLSIDs, IIDs, and type library.

Registering a server

In-process servers such as ActiveX DLLs are passive and can't register or unregister themselves without a little help. You can 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 

REGSVR32.EXE doesn't actually register the server. It simply loads the server and calls a well-known function exposed by the DLL. The DLL responds to this call by writing its required configuration information to the Registry.

Out-of-process servers such as ActiveX EXEs register themselves each time they're launched. If you double-click the icon for an ActiveX EXE, the server launches and registers itself. Once the server process is running, it realizes that it has no client and unloads. 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 

When you build a server using the Make command, the Visual Basic IDE automatically registers it on your development workstation by using one of the two methods just described. When you build an ActiveX DLL, Visual Basic builds in the self-registration code and then registers the server using REGSVR32.EXE. 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 production machine. Both are equally important for your server to run correctly.

The SCM in Action

So, you have an ActiveX DLL with a MultiUse class named CBeagle. The DLL has been installed and registered on the local machine. A client application calls New on this class to activate an object. The Visual Basic runtime translates the call to New into a call to CoCreateInstance and passes both a CLSID and an IID to the SCM.

The SCM starts by looking up the CLSID in the Registry and finding the path to the associated DLL file. The SCM loads the DLL into the client's process. The SCM then calls into the DLL through a well-known entry point and forwards the activation request by passing both the CLSID and the IID.

The server responds by creating a new instance of the requested class. It then passes an interface reference (based on the requested interface) back to the SCM. The SCM simply forwards this interface reference back to the client. Once the client gets this interface reference, it has been successfully bound to the object. In other words, the client has taken hold of one of the object's vTables. The client can now begin making method calls on the object.

As you can see, the SCM is really just a matchmaker. Once it binds a client to an object, it's no longer needed. The client and the object can have a long and fruitful relationship. However, for this architecture to work properly, the SCM must have a predefined way of interacting with the server. Every COM server must therefore provide support for object activation by exposing a well-known entry point through which the SCM can make activation requests.

Servers must expose class factories

The rules for activation support inside a COM server are defined in the COM Specification. COM uses a common software technique known as the factory pattern, in which the code that actually calls New on the class is contained in the same binary file as the class itself. This eliminates the need for the client or the SCM to know about the class definition behind the object being created. The key advantage to this technique is that it allows class authors to revise their code without worrying about client dependencies such as the need to know an object's data layout.

When the SCM interacts with a server to activate an object, it must acquire a reference to a special type of object called a class factory. A class factory object is an agent that creates instances of the class associated with a specific CLSID upon request. A COM server must provide a class factory object for each supported CLSID. When the SCM receives an activation request, it must acquire a reference to the appropriate class factory object. It does this in different ways, depending on whether the server code is in an in-process DLL or an out-of-process EXE. Figure 3-5 shows how a single class factory object can be used to create many instances of a particular coclass.

Every COM server, including those built with Visual Basic, must provide class factories for the SCM. When you compile an ActiveX DLL or an ActiveX EXE, Visual Basic transparently builds in class factory support for each public creatable class. Visual Basic creates class factories in a reasonable and boilerplate fashion. You can't influence how it does this. You can't even see the class factories. You have to take it on faith that they're there. Visual Basic also automatically builds the required entry points for the SCM so that it can get at your class factories.

click to view at full size.

Figure 3-5 The SCM must interact with a class factory object to create instances of a particular component. This design allows the code that is responsible for the creation of objects to remain in the same binary file as the component.

ProgIDs and the CreateObject Function

A ProgID is simply a text-based alias for a specific CLSID. A ProgID consists of two parts. The first part is the name of the server project that holds the component. The second part is the friendly class name of the component itself. An example of a ProgID is DogServer.CBeagle. ProgIDs are especially important for scripting clients. When a scripting client wants to activate an object of a specific type, it usually must know the ProgID ahead of time.

The self-registration code in a Visual Basic server adds Registry entries to map a ProgID to each supported CLSID to support activation for applications such as scripting clients. For instance, there will be a Registry key HKEY_CLASSES_ROOT\DogServer.CBeagle that holds a named value to map it to a CLSID.

You might also see that some ProgIDs have a number on the end in the form of DogServer.CBeagle.1 and DogServer.CBeagle.2. One of this kind is known as a version-dependent ProgID. One without a trailing number is a version-independent ProgID. Office applications and COM servers made with C++ frameworks such as the Active Template Library (ATL) often use version-dependent ProgIDs. However, Visual Basic doesn't support version-dependent ProgIDs. The ProgIDs created by Visual Basic don't get a number on the end. They're always version-independent.

When you call the CreateObject function from a Visual Basic application, you must pass the ProgID for a specific component. However, a client application can't directly activate an object with a ProgID through the SCM. Instead, the client must call a system-level function in the COM library to resolve a ProgID into a CLSID. This is true of the Visual Basic runtime as well as scripting interpreters.

Once a client application has translated a ProgID into a CLSID, it can then call CoCreateInstance. Of course, this is all done behind the scenes. You should see that a call to CreateObject is usually just like a call to the New operator. It's just that the implementation of the CreateObject function requires one extra call to the COM library before it can call CoCreateInstance.

You should be aware of one more difference when you're deciding whether to create objects with the New operator or the CreateObject function. When you call CreateObject, the Visual Basic runtime will always attempt to activate the object through the SCM with a call to CoCreateInstance. This is not always the case when you use the New operator. If one class instantiates an object from another class in the same project using the New operator, the Visual Basic runtime creates and binds the object without any help from the SCM. For example, if you have two MultiUse classes in an ActiveX DLL project and one class creates an instance of the other using the New operator, the SCM doesn't get involved. While this might appear to be a valuable optimization, it can be problematic for components that are configured in either COM+ or MTS. Chapter 6 will introduce configured components and provide a motivation for letting the SCM handle all activation requests. As you'll see, at times you'll want to avoid using the New operator.



Programming Distributed Applications with COM+ and Microsoft Visual Basic 6.0
Programming Distributed Applications with Com and Microsoft Visual Basic 6.0 (Programming/Visual Basic)
ISBN: 1572319615
EAN: 2147483647
Year: 2000
Pages: 70
Authors: Ted Pattison

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net