techvanguards.com
Last updated on 1/25/2016

Servers and Objects
by Binh Ly

Simply put, a server is a container for objects. Servers can be created as DLL binaries (sometimes called in-process servers because a DLL runs “in” the address space of the client) or as EXE binaries (sometimes called out-of-process servers because an EXE runs in a distinct/different/”out”side process than the client).

Some advantages of creating DLL servers are:

  1. DLLs are run “in-process” with the client application and therefore communication between client and server is optimal (maximum performance).
     
  2. DLLs are normally preferred when creating servers that surface some type of User Interface oriented functionality. It doesn’t make much sense to create servers as EXEs when you want the server to create forms/windows that will be “docked onto” or “plugged into” the client application.
     
  3. DLL servers are normally easier to test/debug than EXE servers-which may need to potentially be tested/debugged in remote environments. Testing/debugging DLL servers is almost always done on a single machine and together with the client application.
     
  4. DLL servers are better suited in situations where the DLL binary architecture is advantageous such as when creating servers that need to be extension DLLs for web servers or when your server will be integrated with Microsoft Transaction Server (MTS).

Some advantages of creating EXE servers are:

  1. EXE servers are more robust in terms of fault-isolation. When using EXE servers, a client that goes down (crashes) does not affect the server and vice-versa whereas in the DLL case, if the client or the server goes down, everything goes down along with it.
     
  2. EXE servers are better suited to environments where remote distribution and security are prime considerations.
     
  3. EXE servers are better suited in situations where the EXE binary architecture is advantageous, such as when creating servers that need to be NT services.

Although DLL servers are normally run in-process with the client application, it is possible to "wrap" a DLL server inside another EXE application that will serve as its "host" (or surrogate). By doing this, any DLL server can be used as if it was an EXE server simply because the host executes out-of-process. Microsoft Transaction Server (MTS) is an example of a DLL host application. 

DLL servers are becoming more and more important because with the integration of MTS and COM in the upcoming COM+ and Windows 2000, the servers you will be writing are all DLLs that will be run by a host that's part of the COM+ runtime.

Server Activation

All this time, we've been talking about how a client contacts a server and requests to create a particular object in the server. How exactly does the client do that? Does it load the server manually using ShellExecute? And after the server loads, how does a client reach into the server and ask for an object?

Consider a client that launches a server using the Win32 ShellExecute API (for those unfamiliar with ShellExecute, it's an API that can be used to launch external applications). ShellExecute requires a full path (or at least your application must exist in the system path) to the application that you want to execute. That means that if you move the server to another location, you'll have to rebuild the client application with the new path. A step in the right direction might be to store the path into an external location such as the Windows registry - this way if the server relocates, we change the path in the registry and the client simply reads the new path. Sooner or later, our server might become energetic and may want to relocate to another machine. Now we have a problem because ShellExecute will not work anymore, i.e. you cannot use ShellExecute to execute an application from another machine. We'd have to somehow develop a new application that would handle a cross-machine ShellExecute. Again, a step in the right direction here would be to leave it to the experts to write the application that would handle cross-machine activation. Once that is available, we simply use it in our application.

Server location is an implementation detail that we don't want to waste a lot of time working on. COM helps a bit in this area by defining a standard means of 1) how servers tell clients where they are and 2) how clients activate servers without getting tied into the gory details of location issues.

Registration

A server tells the world where it is located using a process called "registration", i.e. the server gives COM information about its location. Under Windows, this location information is stored in the registry - a conventional place to store application settings. Before we go further, let's recall that a client is not really interested in the server itself, per se; the client's interest is more towards the objects that are contained in the server - after all it's the objects that actually expose interfaces and it's the objects that do the work for the client. Therefore, what we really need stored in the registry is a list of server objects and under each object, a subkey that points to the physical location of the server file. This way, a client that wants to use a particular server object will say "Hey COM, I want to use this object named Foo." COM then goes into the registry, looks for Foo and if it finds it, looks under its subkey to get the actual server filename, and then launches the server. That's really the gist of server activation in COM.

Let's look at some advantages of the activation mechanism described above:

  1. Clients do not have to deal with registration details. The registration process is done by the server. This means that the server must have some code in it that gives the necessary registration information to COM. This process commonly involves writing entries into the Windows registry. 
     
  2. If the server relocates, you simply register the server again. This way, the registry will contain updated information about the server's whereabouts. The next time the client needs an object from the server, COM will know exactly where to look for the server.
     
  3. The client is rather "detached" from the server. The client simply tells COM which object it wants and COM takes care of scanning the registry, finding the server, and then activating the server. Why is this important? Recall our example of the energetic server that wants to relocate to another machine. Using the registration mechanism, this energetic server will register itself with a location pointing to a machine name or network address of the machine it resides in. When the client later asks for an object from that server, COM will look in the registry, find a remote machine address, establish a connection to the remote machine, and then activate the server on the remote machine. In short, the client doesn't really know nor care about the exact location of a server; all it knows is it wants a server object, it tells COM about it, and COM takes care of the rest!

In order for a server to register its objects, there needs to be a way to identify or name server objects. Similar in concept to our previous discussion on interfaces, each object also has an ugly name and a friendly name. If you recall, the ugly name is a sequence of numbers that guarantees name uniqueness and the friendly name is the one that makes it easier for clients to remember. Since the ugly name's purpose is to be able to uniquely identify objects (and interfaces), it's time to introduce a more appropriate term for ugly names: globally unique identifier or GUID for short. GUIDs in COM are generated using an algorithm that guarantees statistically unique values. Interface GUIDs are also called interface identifiers or IIDs and object GUIDs are also called class identifiers or CLSIDs.

Going back to the registration process, a server registers itself by handing out the CLSID of each of its objects. In the registry, each of those CLSIDs will contain a subkey that points to the physical server file. For example, if you have a server named FooServer.exe (assume FooServer.exe resides in C:\) that contains 2 objects Foo and Bar, this is how its registration looks like theoretically:

<CLSID registry section>

... other CLSIDs ...
CLSID of Foo
CLSID of Foo\ServerLocation = "c:\FooServer.exe"

CLSID of Bar
CLSID of Bar\ServerLocation = "c:\FooServer.exe"
...other CLSIDs...

A client that wants to create Foo simply passes COM Foo's CLSID and COM will take care of scanning the registry to locate and activate FooServer. Since CLSIDs are ugly names, the client might prefer to work with friendly names. By convention, server objects have friendly names using the <ServerName>.<ObjectName> format. For our Foo object, that would be "FooServer.Foo" and for our Bar object, that would be "FooServer.Bar." Friendly names for objects are termed programmatic identifiers or PROGIDs. 

To enable clients to specify PROGIDs when creating objects, PROGIDs also get registered. Theoretically, this is how PROGID registration looks like:

<PROGID registry section>

... other PROGIDs ...
FooServer.Foo = CLSID of Foo
FooServer.Bar = CLSID of Bar
... other PROGIDs ...

Notice how the PROGIDs simply point to the corresponding CLSIDs. So when a client asks COM "Create me a FooServer.Foo object", COM looks in the registry for "FooServer.Foo" in the PROGIDs section, finds it and sees that it maps to Foo's CLSID, follows that mapping into the CLSIDs section and finds the correct server to activate. Pretty cool huh?!

All this business of PROGIDs and CLSIDs pretty much sum up the activation process for COM servers. If you were paying attention, it's easy to see that the entire activation mechanism is almost made transparent to clients - all the client needs to know is the CLSID or the PROGID of the object it wants and then it lets COM take care of the rest. Location transparency is the term that COM uses to describe this entire concept and process.

Object Creation

After COM activates a server, the next thing it needs to do is to somehow reach into the server for the object that the client wants. Again, COM steps in here and defines a standard protocol for servers to "hand out" its objects to clients. The protocol is extremely simple and works this way:

  1. A client wants to create an object from a server. This client asks COM to create that object. COM does a registry lookup to locate and activate the corresponding server as described previously.
     
  2. Once the server is up and running, COM asks the server for an "object creator" object. The term "object creator" means that this object creates other objects. In particular, an object creator creates the actual object that the client wants.
     
  3. The "object creator" object exposes a standard interface that contains a method, CreateInstance, used to create the actual server object. COM (or the client) then calls CreateInstance to create the server object, gets back an interface pointer, and then runs all the way back to the client with the interface pointer.

Figure: Standard server object creation protocol

Class Factory

The "object creator" described above is what is called a class factory in COM (many people believe that the term "object factory" is more appropriate since it creates objects, not classes). A class factory exposes a standard interface named IClassFactory which contains the CreateInstance method, among others.

IClassFactory = interface
  procedure CreateInstance;
  ...
end; 

This class factory protocol is enforced by COM, meaning that every server must:

  1. Implement a class factory's function correctly. Each type of object in a server requires is own class factory. This enables the server to hand out the correct class factory required to create a given object that the client asks for.
     
  2. Implement class factories as if they are regular server objects. In other words, since a class factory exports an interface (IClassFactory), it is a bonafide COM object. This means that class factories should reference count and also support vtable binding.

To make things more concrete, lets consider what happens exactly when a client asks to create Foo from our FooServer server example:

  1. Client asks COM "Create me a FooServer.Foo object"
  2. COM goes to the registry and finds the translation path from "FooServer.Foo" to Foo's CLSID
  3. COM activates FooServer.exe
  4. COM reaches into FooServer.exe asking "Give me a class factory for this object", passing in Foo's CLSID so the FooServer knows which class factory to hand out. Remember, every class in  FooServer has a corresponding class factory, i.e. there is a class factory for Foo and another class factory for Bar.
  5. Given Foo's CLSID, FooServer finds the corresponding class factory and, if found, hands the class factory's IClassFactory pointer to COM
  6. COM calls IClassFactory.CreateInstance to create an instance of Foo. As a result, IClassFactory.CreateInstance returns an IFoo pointer.
  7. COM takes the IFoo pointer and runs back to the client with it

Figure: Server object creation using the Class Factory

As a side note, COM also provides a mechanism for the client to directly ask for an object's class factory. In this case, COM will go through the same exact steps above but stops at step 5. After step 5, COM will then run back to the client with the IClassFactory pointer. The client can then manually call IClassFactory.CreateInstance to create instances of the server object.

Developing class factories is tedious and is not for the faint of heart. Luckily, today's development environments hide a lot of the complexity involved in creating class factories. For instance, Visual Basic completely hides the class factory protocol that not a lot of VBers even know of its concept!

Where Are We?

We've just seen the basics of servers, server activation, and how objects are created. Knowledge of these two concepts are important and useful milestones in becoming an effective COM developer.

Further Reading

  • Understanding ActiveX and OLE by David Chappell
  • Inside COM by Dale Rogerson
  • Essential COM by Don Box
Copyright (c) 1999-2011 Binh Ly. All Rights Reserved.