Steve Robinson and Alex Krassel
Panther Software
August 8, 1997
Download this document (in Microsoft Word format) with associated sample files (zipped, 407K).
Contents
Overview
Purpose
What You Will Need
Part 1: Duplicating Interfaces
Part 2: Mixing Class Inheritance with Interfaces Inheritance
Unlike every other industry, traditional software development requires application executables to be compiled and linked with their dependencies. Every time a developer wants to utilize different processing logic or new capabilities, he or she needs to modify and recompile the primary application to support them.
In manufacturing, this limitation would never be allowed. Could you imagine if you had to rebuild your car engine if you wanted to change your tires from the ones the manufacturer issued to high-performance tires? While this might appear to be a windfall for mechanics, the excessive maintenance costs would actually diminish demand for automobiles, and everyone -- consumers, automobile manufacturers, mechanics, and parts manufacturers alike -- would suffer. In fact, one of the keys to the success of the industrial revolution was the ability to "interchange machine parts," that is, the use of components. Today, we change components and plug new accessories into our cars without even giving it a second thought.
Cars know nothing about the tires they use. Tires have properties (wheel width, etc.). If the properties are consistent, they are interchangeable. Lighting fixtures know nothing about the light bulbs they use. If the properties of the light bulbs (diameter of the base that screws into the socket) meet the requirements of the fixture manufacturer, they are interchangeable. Isn't it about time the software industry catches up with the rest of the world and builds components that require no knowledge of how they will be used! For an industry that thinks it is so far advanced, we are actually way behind.
At first glance, dynamic-link libraries (DLLs) seem to provide a solution for the problems listed above. The following fictitious story will demonstrate why this is not so:
You need to develop an application for Acme Gas Tanks. The application will display the level of gasoline in Acme's new prestigious 1,000-gallon fuel tank. First, you create an ActiveX gauge control that shows three data points: the tank's current level, the lowest possible safe level, and the highest possible safe level. You write a DLL called GasTankLevelGetterDLL, which exports the following functions:
Internally, GasTankLevelGetterDLL supports the device capability to read data continuously from the new 1,000-gallon Acme gas tank. Your application works like a charm and ships "bug-less."
A couple weeks later, Richy Rich calls you up and says your ActiveX gauge control is the prettiest thing he has ever seen in his life. Richy says he wants to use it to monitor the level of his 5,000-gallon fish tank. He says the gauge needs to show the same three levels as your gas control. You tell him you will get back to him tomorrow while you think it over.
The next day, you conclude that you are going to name every DLL the same LevelGetterDLL, which will export the same three functions with different internal processing. Richy's fish tank monitoring problem is solved. He uses your application 24 hours a day to be sure his fish are safe and secure. You ship a new LevelGetterDLL to Acme as well. Other companies contact you about using your ActiveX gauge control. You respond by saying, "No problem. Export these three functions, name your DLL LevelGetterDLL, and you are ready to go." You had to recompile your application once to support the new LevelGetterDLL -- but as long as everyone in the world names their DLL the same (LevelGetterDLL), and uses the same three immutable methods, everything will work perfectly, and you will never have to recompile your application again. You go home that night feeling very smart.
The next day you open The Wall Street Journal to find that Richy Rich has died in a helicopter accident. While en route to Rich Inc. headquarters, Richy's helicopter ran out of gas. It seems that Richy was an Acme client, and he ran both applications on his PC simultaneously. Application 1 was the fish tank monitor you developed for him using the LevelGetterDLL. Application 2 was Acme's fuel tank monitoring application that also used a version of LevelGetterDLL, the one shipped with Richy's helicopter. Although Richy ran both applications, Application 2, Acme's fuel tank monitoring application, used the fish tank's LevelGetterDLL and reported the levels in the fish tank instead of Acme's 1,000-gallon gas tank, because the version for the fish tank was copied to the computer last. Richy never knew that his helicopter was about to run out of gas. The Rich estate sues Acme who, in turn, sues you. The other companies you advised decide to sue you as well. Had you used Component Object Model (COM), Richy Rich would be alive today, and you would not be in the courthouse.
Rule: If two or more DLLs export the same functions (immutability), you can link either. However, a single application cannot use both DLLs, nor can they both reside on the same computer. COM solves this problem. Two COM servers with identical interfaces (and therefore methods) can be used by two different applications and can reside on the same computer, because they have different CLSIDs and therefore are different binaries. Further, the two COM servers are technically interchangeable.
The inability to "interchange machine parts" (components) has been tolerated in software development, because of the youth of our industry. However, just as the Industrial Revolution created independent machine parts, COM does that for software components. By understanding CLSIDs and interface immutability, one can write a complete plug-in without any knowledge of the client. This means Application1 can run using either Plug-In1 or Plug-In2. Better yet, Application1 can dynamically switch between Plug-In1 and Plug-In2. Designing applications to use dynamically interchangeable plug-ins will do for software development what machine parts did for the industrial revolution.
With all the excitement over Active Template Library (ATL) and Distributed COM (DCOM), we tend to forget that solving these types of problems is the primary reason why COM was developed. DCOM's ability to leverage remote procedure calls (RPC) and marshal data is a very popular perk (and may be the reason why COM has grown in popularity during the past 12 months), but it is not the primary reason COM was developed. COM was developed so software manufacturers could plug new accessories into existing applications without requiring a rebuild of the existing application. COM components should be designed as interchangeable plug-ins whether the COM component is a local in-process DLL or a remote server executable.
This article will demonstrate COM plug-in components that allow reuse in ways analogous to automobile tires. Using COM allows software product lines to be developed in significantly less time, as compared with not using COM! Knowing how to create COM objects and interfaces is the key to building interchangeable plug-ins.
Through the course of this article, we will develop a series of interchangeable COM plug-ins:
To build the samples, you will need Microsoft Visual C++® 5.0. While you do not need 10 years of Windows® and C experience, you do need to have some experience with Visual C++, MFC, inheritance, and polymorphism. The samples will build and run under either Windows NT® or Windows 95. We will use the OLE/COM Object Viewer, a handy utility that ships with Visual C++ 5.0 and Visual Basic® 5.0, and is also downloadable from Microsoft at http://www.microsoft.com/com/resources/oleview.asp .