Gigantic mainframes run monolithic software. With client/server and distributed computing technologies, you can distribute work and develop less monolithic applications. Before you learn about distributed components, let’s first examine how to make software less monolithic.
One technique is simply to develop special purpose modules, or static libraries. Applications use these static libraries by linking with them. With static libraries, you gain reusability at the source level. Notice that you also gain maintainability with a static library, because it contains a lump of code with a well-defined purpose. You build applications by integrating a number of these static libraries, allowing your application to become less monolithic. That’s fairly good—but not good enough.
Unlike static libraries, component technology supports the notion of pluggable components. Look at Figure 1-6 and consider this scenario. I have two mother boards that I want to sell. Board A has all the peripherals (CPU, RAM, video, sound, modem) built-in. Furthermore, there are no expansion slots. Board B is a Pentium 60, upgradable to Pentium 90. It has 16MB RAM, expandable to 128MB. And by the way, it has 3 ISA slots and 4 PCI slots. If you buy board B, I’ll throw in a cheap video, modem, and sound card. Board A cost $40 and board B cost $50. Which mother board would you buy? Of course, you’d buy B. Why? Because A is hideously monolithic, and B supports the notion of component plug-ins.
This is exactly the goal of component technology. Unlike monolithic software, a component is a piece of software with a well-defined purpose. It is easily maintainable and reusable at the binary level. Note the drastic difference between a component and a static library; unlike a static library, a component can be reused at the binary level. This means you can buy a component and plug it into your application, without ever needing a header file or an import library! This plug-in aspect of software integration is truly black-box programming. You don’t have to know anything about the code in a component to use it.
One of the advantages of a component technology is that it has no bias toward language, tools, operating systems, or network; that is, it can interoperate regardless of differences in these domains (or at least, this is the goal). As shown in Figure 1-7, software components, usually dynamic linked libraries (DLL files) or executables (EXE files), are built from COM classes whose runtime instances are referred to as COM objects (or more generally, distributed objects). COM objects are based on the rules of the Component Object Model (COM). A COM object exposes one or more COM interfaces to support the services that it provides.
In Figure 1-7, a box represents a COM object and “lollipops” represent a COM object’s exposed interfaces. Clients use a COM object via its contractual, published interfaces. The clients know only of interfaces; they must not care about implementation details. So, component technology takes the separation of interface and implementation to the fullest to achieve black-box programming.
Component is a nasty word because one person may use it to refer to an object and another may use it to refer to a binary module. To be consistent, this book uses the term COM component (or simply component) to refer to a binary module, such as a DLL or an EXE. It uses the term COM object to refer to a runtime instance of a COM class whose source code is linked into a COM component. In general, a COM class is an object definition in source code that implements at least the IUnknown interface. To translate this into C++, a COM class is simply an abstract data type (class definition) for an object that implements the IUnknown interface.
Let’s look at a simple component imaging architecture that is based on Distributed COM. As shown in Figure 1-8, this architecture includes the following distributed components, which can interoperate seamlessly with one another regardless of their locality:
Can use the services exposed by server components without the need for the server component’s source code or an import library. This is reuse at the binary level. Examples include Microsoft Internet Explorer or any other application.
Exposes a CoViewer COM object that can display any data blob, such as a TIFF image or a Microsoft office document. This component is a DLL, so it can be dynamically loaded into any client component. A client component that embeds the
BlobViewer component can take advantage of an already built and tested viewer that can render a variety of data blobs.
Exposes two COM objects for language checking: CoDictionary and CoThesaurus. The
BlobViewer and the
OCRServer components can use the services exposed by the
LanguageServer to check for spelling and lookup synonyms and antonyms.
Because these components can be distributed among many computers, a communications mechanism is needed. If you were to use TCP/IP to support the interaction among these components, you would be writing application-specific protocols until you were blue in the face. But if you use a component technology, such as Distributed COM, you will gain the important benefits of distributed and plug-in interoperability. This is because Distributed COM supports location transparency and binary interoperability.
Let’s study these components in further detail. The
OCRServer EXE shown in Figure 1-8 is a component. It is a well-defined piece of software that can be deployed and reused. Yet, it is simple to maintain because it is small and has a well-defined purpose: to perform optical character recognition for requesting clients. It houses a COM object, called CoOcrEngine, that exposes the IOcr interface. It is this interface that allows clients to perform OCR.
BlobViewer component is itself a stand-alone piece of software. It houses a COM object that exposes an IViewer interface, allowing clients to programmatically control the
BlobViewer. A COM interface may support both properties and methods. Interface properties are accessors; in other words, they are getters and setters. For example, the IViewer interface can expose a property called
ShowToolbar that allows a user to set or get the visibility of the
BlobViewer’s toolbar. A user can hide or show this toolbar by setting the
ShowToolbar property to false or true, respectively. Aside from properties, an interface is largely made up of methods. An example of a method in the IViewer interface can be ZoomIn, which allows a client to tell the
BlobViewer to enlarge an image.
COM also supports the notion of events, also called notifications. Assume that the IOcr interface supports a method that’s called ProcessImageForTwoHours (who cares what it does). When this method is invoked, the user of the client component surely wouldn’t want to stare at the frozen user interface for two long hours. To solve this problem, you use COM’s support for events. By using this technique, a client asks the server to notify it when the image has been processed. Immediately after the image has been processed, the server notifies the client of such an event. In this particular case, the notion of events saves two valuable hours and liberates the user from staring at a frozen application.
As shown in Figure 1-8, components can be widely distributed. Since a component in cyberspace can be both a client and a server, the notion of clients and servers weakens. Thus, you tend to leave off the client/server buzzword and simply call the COM objects (within the software components) distributed objects.