In the early days of personal computing, programmers diligently wrote procedural code to create character-based PC applications, knowing individual users would run separate copies of the program on their own machines. Programmers rarely needed to be concerned about how a program would communicate with other code running on the local machine, let alone worry about how their program would interact with other programs running on a remote machine. Times change quickly.
Today, the computing power available from a typical desktop computer can well exceed the computing power of the early room-sized computers. It is now common-place for Internet-enabled computers to browse data from hundreds of thousands of computers intercon- nected around the globe. Now that our computers can readily connect to each other, programmers need the tools and a unified architecture that enables us to write modular programs to communicate in a standard, organized fashion. Object technology provides the power to deliver this functionality, and Visual Basic gives you powerful tools to implement it. This chapter gives you a broad overview of the object technology foundation Microsoft has made available to you for developing Windows applications using Visual Basic. Object technologies need an underlying framework that can enable complex and interrelated programs to work together in harmony. The object architecture Microsoft created is called the Component Object Model, or simply COM. COM is the foundation underlying OLE and ActiveX technologies and is Microsoft's solution for providing object-to-object communication. With the enhancements of the Distributed Component Object Model (DCOM), this technology even provides communication services across networks to objects physically located on different machines.
A basic understanding of COM, OLE, and ActiveX is vital for your success in programming with Visual Basic. You can still use straight programming language to create procedural code, and sometimes that's necessary and appropriate, but doing so can limit the benefits of using an object-oriented design. This chapter focuses on object-oriented foundations.
Users of custom corporate computer applications continue to demand more functionality from their aging systems, and they are becoming less patient about waiting for new functions to be implemented. Because legacy computer systems tend to be monolithic collections of original code, maintenance code, integration code, and lingering "quick fix" code, these applications have become dramatically more difficult and costly to maintain. Changing key sections of these fragile constructions to add new features is fraught with peril, especially if these applications are part of a mission-critical system. To make matters worse, such applications might be only sparsely documented (if at all), and only rarely are any members of the original programming team still working for the company. If you've ever been faced with the task of extending the life of such an application, you know there must be a better way to build and maintain enterprise-wide computer systems. Fortunately, there is.
Suppose your company, like many companies, has a legacy application that might give unpredictable results for dates beyond the year 1999. Suppose that instead of being a single, massive collection of procedural code, this application was built from functional modules. Each module has a very specific purpose, and calling that module is the only way for the application to perform that specific task. Thus, in this scenario, one dedicated code module has the task of performing all date calculations, and any other module in the entire application relies on that module for any and all date calculations. To test the capability of this program to handle dates beyond the year 2000, you only have to test that date-calculation module to see what happens. If it needs repair, you can change the implementation inside of that module, test it, and then plug it back into the program to complete your task. If the date storage and retrieval module also poses a problem, you can use a similar procedure to test, repair, and restore that module. Instead of this scenario, however, most companies are paying staggering costs to update hundreds of thousands of lines of legacy code, in some cases line-by-line.
With the pace of change in today's business world, companies can no longer afford to create systems that cannot be updated easily. This modular approach exists and is available now.
These magic little modules are known abstractly as Objects. C omputer-modeled objects can be implemented in software as discrete components that can be created, modified, or reused as building blocks for complex, enterprise-wide applications. Rebuilding existing functionality from scratch is admittedly quite costly and time-consuming. However, after the foundation is in place, you no longer have to start over repeatedly from scratch to create each new application because a large portion of your desired functionality can be constructed from previously built components.
Imagine how long it would take for new computers to be developed if computer hardware manufacturers had to start from scratch every time they wanted to build a new computer. Many of the components of a new computer don't require changes since the last model was released, so existing components (such as power supply, video card, network interface card, or modem) are simply reused. Eventually, when new designs are implemented, the new item can easily replace the previous model, with perhaps a modification of that particular interface, but without having to redesign the entire computer.
After the computer is sold to a user, upgrading a component is also simplified by this modular approach. For example, suppose you want to upgrade to a faster modem. You can select your new modem from a variety of vendors, and any modem supported by your operating system will work just fine. You don't need to worry if the modem will work because modems have become a commodity by using standard interfaces and communication protocols.
Software developers are now seeking the tremendous advantages of component architecture that hardware developers have enjoyed for years. By using component-based software architectures, you can implement object-oriented designs, yet maintain a large degree of language neutrality. This enables corporations to make use of existing programming talent in any language supported by the object model implemented on their computer platform. This language neutrality is accomplished by using programming tools that create components that are compatible at the binary level. This process enables programmers around the globe to independently develop components or entire applications that will properly communicate together, as long as the interfaces defined between them remain consistent. These components might originally be created for internal applications, but they can then be shared within the company or even marketed to other companies who have similar needs.
If you are not already familiar with object-oriented programming, you will likely need to invest some time to learn how to implement this new technology effectively. The effort you spend now to become proficient with this technology will reward you with increased productivity, job satisfaction, and job security.
After your development team invests the initial time and energy required to implement object oriented development, the advantages to your company are also worth noting:
You might be wondering if your favorite programming language is able to support object-based application development. A major benefit of COM is that it defines a common binary standard. This means that COM defines an interface as a low-level binary API based on a table of memory pointers, which then enables code modules from different COM-compliant compilers to operate together. Theoretically, a COM-compatible compiler can be built for any programming language that can create memory structures using pointers and can call functions through pointers. Visual Basic includes a COM-compatible compiler.
The result is that client objects implemented in Visual Basic can call on the services of server objects written Visual C++, and vice versa. Each language has certain advantages when creating COM objects, but when used together, they are a powerful combination with which you can tackle nearly any programming challenge you encounter.
Object-Oriented Programming (OOP) presents a revolutionary improvement in the architecture and tools used to build and maintain computer applications. There are a variety of object-oriented methodologies to choose from, and most are named after the individuals who proposed them. If you want to investigate them in detail, today's leading object-oriented methodologies include Booch, Coad-Yourdon, Jacobsen, Martin-Odell, Rumbaugh, Shalaer-Mellor, and Wirfs-Brock. Despite this wide variety of methodologies, the underlying OOP concepts are essentially the same. To preclude any confusion over the meaning of the OOP terms used in this book, this section provides a brief review of common terms and notation used in object-oriented programming.
Understanding Objects An OOP object can be thought of as a programming entity that in many ways resembles a physical object. OOP objects typically have properties that describe their attributes, and methods that specify their behavior. Properties of OOP objects can be much like properties of physical objects, describing attributes such as color, cost, or size. The programmer can set and lock these properties when designing the program or can make them available to the user to change during run time. Methods are the named functions an object is programmed to accomplish when called. Methods are invoked by referencing the object and the method's name. When called, the object behaves as defined by the method to obtain, manipulate, or destroy program data, without any requirement to reveal how these tasks are accomplished. Objects are portable, which is to say they can be used without modification in any environment where they are supported. Objects are reusable because you can use the same object to perform the same task in different programs. Better yet, you can use objects other people have created if you want to quickly add a standard service or function to one of your own programming projects.
For the purpose of object-oriented programming, an individual object is a particular instance created from a particular class of objects. A class is a set of objects that you define to have the same attributes and behavior. Classes can be very specific or very general, depending on your needs. For example, you could define a class that encompasses all writing implements, or you could define a class that only represents wooden yellow pencils with number two lead. The class structure defines a generic blueprint or model of a new object of that class. Every OOP object belongs to a class, and is completely defined by its class structure.
To actually bring an object to life in the computer, a specific instance of the object must be instantiated, which is to say a new member of the class is created in memory.
When you define a class in code, you must define all of the object's methods, data structures, and interfaces. By default, the methods and data are reserved for the object's exclusive use and are declared as private. If you want to make methods and data available for direct manipulation by other objects, you can declare them as public when you define them in the class structure. Typically, you would define any methods and data you wish exposed for clients or used by the user interface as public, and define the internal elements that implement those services as private.
NOTE: C++ programmers have been programming by using object classes for years, but the class structure is a relatively new addition to the Visual Basic language. Support for object classes began with Visual Basic 4 and is further expanded in Visual Basic 5. As more wizards, integration options, and powerful C++ features (such as classes) have been added to Visual Basic, the stigma against using early versions of VB for anything but simple projects has vanished. Visual Basic is now quite suitable for creating robust and reliable enterprise-wide applications.
The OOP purists remind us that for an object to qualify as a "true" OOP object, it must support the characteristics of encapsulation, polymorphism, and inheritance. These distinctions are discussed in detail during the examination of the differences between object-based and object-oriented programming later in this chapter (see "Object-Oriented versus Object-Based Programming").
An object that provides services to another object is acting as a server. The object using those services is referred to as the client. An object can be both a client and a server at the same time; that is, it can request the services of one object while providing services to yet another object. For clarity, it usually is best to focus on a single relationship between two objects at a time, and denote one as the client and the other as the server.
Object Relationships When trying to design objects, it is handy to have a common method for representing these programming objects graphically. The syntax <object>.<method> can be used to denote invoking a particular method of the server object. There are many variations, but the typical way to represent an object graphically is represented in Figure 23.1.
FIG. 23.1
A COM object contains all the methods, properties, and data required by that
object.
In Figure 23.1, the object and its contents are depicted as rectangles. The interface nodes as shown as circles connected to the object by a straight line extending from the object. Connections to the interfaces are implemented by using memory pointers, which can be drawn as arrows extending from the client object to the interface node of the server object.
When the client and server are operating in the same process space in the computer, the server is referred to as being in process, or as an in-process server. In-process servers provide the fastest possible service to the client. In-process servers are typically objects in the same program, or objects loaded into the same process space ahead of time from an external source such as a Dynamic Link Library (DLL) file. The relationship between a client and an in-process server is shown in Figure 23.2.
FIG. 23.2
This client object has obtained the services of a server object on the same
computer and in the same process space. In this relationship, this server is a local,
in-process server.
It is also possible for the server object to operate on the same computer as the client object but in a separate process space. In this situation, the server is called a cross-process or out-of-process server. Because there are two ways to have an out-of-process server, however, it is more specific to refer to this as a local server. For example, your spreadsheet becomes a local server to your word processor when you copy a table of numbers from the spreadsheet and paste it into your word processor. This relationship is depicted in Figure 23.3.
FIG. 23.3
This client object has obtained the services of a server object on the same
computer, but outside of its process space. In this relationship, this server is
a local, out-of-process server.
The other way an out-of-process server occurs is when the client and server are on different computers. This once-rare situation is rapidly gaining in popularity as objects are distributed across computer networks. The out-of-process object providing the service in this case is called a remote server. Performance is typically slower than with in-process or local servers, but the gains in functionality and scalability can be revolutionary, as will be highlighted later when discussing distributed computing. The remote server relationship is depicted in Figure 23.4.
Objects that perform very specific tasks can be conveniently grouped together in a variety of ways. These object-grouping techniques are an effective way to reuse objects and minimize maintenance.
FIG. 23.4
This client object has obtained the services of a server object on another
computer on the network. In this relationship, this server is a remote, out-of-process
server.
Perhaps the most straightforward way is to create a new object to act as a container in which you place the reused objects. This is referred to as object containment, because the outer object completely contains the inner objects. The interfaces of the inner objects are only visible to the outer object and cannot be accessed directly by external objects. This relationship is depicted in Figure 23.5.
FIG. 23.5
This outer object completely contains the interfaces and services of the inner
objects. The inner objects are a collection of re-used and new objects that clients
can access via communication with the new outer object
A related grouping method is created by starting with a containment relationship but allowing the outer object to pass along or delegate the connecting pointer from the client object directly to the inner object needed to implement the desired function. This is referred to as object delegation and is illustrated in Figure 23.6.
FIG. 23.6
With object delegation, the client object first obtains a pointer to the external
interface of the server. The server then provides the address of the inner object
that can provide the requested service, and the client connects directly to the inner
object.
Finally, objects can be collected together, or aggregated, by an outer object that allows the inner objects to directly expose their interfaces to client objects. This is perhaps the most complicated case to implement, because the clients of the inner objects do not directly see the relationships between the inner and outer objects. This is referred to as object aggregation and is described in Figure 23.7.
FIG. 23.7
With object aggregation, the client object can directly access the exposed
interfaces of each inner object in the server's collection.
Procedural programming is an approach where you determine the steps needed to solve a problem and implement them by creating the algorithms in code that act upon the data and store the resulting output separately from the algorithms. Object-Oriented Programming (OOP) is an approach where you can group a code module's related data and implementation code together into a unified structure, and it acts together in response to requests from other objects. SmallTalk and C-based software development languages have provided this OOP capability for years but are extremely cumbersome for all but dedicated experts. Today you can use Visual Basic (and Visual C++ and Visual J++, either separately or in combination) to create robust, enterprise-wide, object-oriented applications.
In object-oriented programming, a discrete combination of code and data must represent each programming object. OOP also requires a way for client objects to dynamically create new instances of objects based on a given class, and to create and destroy server objects as needed, while the application is operating. In comparison, although object-based languages enable you to create object-like structures in code, creating another similar object means you must create it at design time, perhaps cutting and pasting code from the first object.
Furthermore, to qualify as a true OOP object, the programming structure must also support the characteristics of encapsulation, polymorphism, and inheritance. Entire books have been devoted to this, but for the purposes of this chapter, a brief explanation and some simple examples will suffice to illustrate the basic concepts.
Encapsulation occurs when the desired services and data associated with an object are made available to the client through a predefined interface without revealing how those services are implemented. The client uses the server object's interfaces to request desired services, and the object performs on command without further assistance. For example, suppose you are developing a Domestic Simulator application. You might want to create a "spouse" object class where you define methods for common household tasks, such as methods called "WashDishes" and "MowTheLawn." If a client object (perhaps even another spouse object) determines it is time to invoke WashDishes, it can make that request via the interface you have appropriately named IWashDishes. The desired result might be that any instantiated "Dishes" object with a "Clean" property value currently set to False would be cleaned and set to True.
How this method is accomplished is irrelevant to the client. The WashDishes method can be performed with the time-honored "Wash in Sink" procedure or with any version of the popular "Automatic Dishwasher" procedure, but the end result will be the same. Similarly, the interface IMowTheLawn would be used to invoke the MowTheLawn method. In this case, the server object might be programmed to trim all "Grass" objects with a length property of greater than 3 inches down to an even 3 inches. This MowTheLawn method can use the slow but reliable "Rotary Mower" procedure, the faster "Power Mower" procedure, or the coveted "Riding Mower" procedure, but the end result will be the same. In each case, the simulated grass will be an acceptable length, and the client will have no need or desire to know how it got that way.
Objects can even encapsulate their own data from all other objects. By default, an object's data is considered private, and clients must call one of the object's methods to manipulate data or report data values. It is sometimes practical or necessary to expose the data to for direct manipulation by client objects, and this is accomplished by adding the public declaration when you define the object's class structure.
COM fully supports encapsulation, as COM permits a client object to access a server object only through its well-defined interfaces.
Polymorphism is the capability for different kinds of objects to respond appropriately and differently to the same stimulus. In OOP, polymorphism can be seen as the capability for client objects to access the services of different server objects in the same syntactic manner, even when dealing with different types of objects. An illustration should help clarify this concept.
Returning to the Domestic Simulator program, suppose that to make the program more realistic, in addition to the "Spouse" class of objects, you also define object classes representing "Child" and "Dog." Suppose you have your program instantiate a Spouse object called "Katarina," a Child object called "BabyAlex," and a Dog object called "Rover." Still trying to make it realistic, you can individually define methods for each of these objects to appropriately simulate the behaviors of walking, eating, sleeping, and speaking. You can then have your program make a call to Rover.Speak and compare that result with a call to Rover.Eat to see if Rover's bark is worse than his bite. However, asking the spouse object to "Speak" by making a call to Katarina.Speak should give you a dramatically different result than the one produced when you call the Dog object to Speak. Asking the child object to Speak by using BabyAlex.Speak might give little or no result until the Domestic Simulator has run for a sufficient amount of time. Although all these requests are made in exactly the same way--by using the syntax <ObjectName>.Speak--the request produces different results, depending on the type of object. This is polymorphism in action.
COM supports polymorphism by allowing different classes of objects to support interfaces with the same name, while allowing these objects to implement the interfaces differently.
Inheritance is the capability to define increasingly more specific objects, starting with the existing characteristic definitions of general objects. Thus, when a more specific class of objects is desired, you can begin the definition by first inheriting all the characteristics of another defined class and then adding to them. Again, an example is useful to help explain this concept.
Again returning to the Domestic Simulator program, you can first define a class of objects called "Animal" to represent the common attributes of all animals in your simulation. Suppose the Animal class includes characteristics such as breathing and eating. From the Animal class, you can create sub-classes for "Wild_Animal" and "Pet_Animal." Both Wild_Animal and Pet_Animal objects automatically knows how to eat, but now you can define that Pet_Animal objects will eat only from their food bowls, whereas Wild_Animal objects will eat food wherever they find it, including your simulated garden and simulated trash containers. By adding specific characteristics in addition to the common characteristics of the Pet_Animal class, you can create classes for "PetDog," "PetCat," and any other kind of pet you want to share the house with in your simulation. With all this additional behavior defined in higher level classes, your previously mentioned polymorphic pet Rover can now be instantiated from the "PetDog" class, having completely inherited all the common characteristics you defined for a Pet_Animal and for Animals in general.
COM supports interface inheritance, which is the capability for one interface definition to inherit characteristics from another interface. COM, however, does not support implementation inheritance, which is the capability for one object to inherit the actual implementation code from another object. Implementation inheritance makes sense when an entire application is compiled in the same language. Because COM is standardized at the binary level, and not the language level, passing the actual implementation code between objects would produce unpredictable and potentially disastrous results. This subtle but important difference has sparked an ongoing debate about whether COM truly supports inheritance. For comparison, the CORBA specification does not require implementation inheritance either, but some CORBA implementations have supported it for certain special cases. In practice, being able to integrate objects created from different development languages is extremely useful, and greatly outweighs this one concession to theoretical OOP purity.
In the long run, the debate over whether a language is truly object-oriented becomes merely an academic exercise. What matters to you, the programmer, is which characteristics you need for a particular programming project, and what language or combination of languages provides the easiest, fastest, and most efficient way to implement them.
In recent years, there has been incredibly rapid progress made in advancing the techniques and technologies used for Object-Oriented Programming and modular program communication. These new and improved technologies with all their new names and integration requirements have also brought their share of confusion to the programming community. A brief look at the most recent steps should help clear away some of that lingering confusion.
In 1993, Microsoft created the Component Object Model (COM) and laid the technical foundation that has dramatically improved object communication in the Windows environment. COM provided both the technical specifications for creating compatible objects and the communication "plumbing" in the Windows operating system required to make it work. The first use of this new programming model came when Microsoft completely rebuilt OLE functionality using the new COM architecture. Instead of using a messaging protocol built on top of the operating system (like DDE or OLE 1.0 before it), COM provided interprocess communication (IPC) directly as a service of the operating system. Although it was a very different product and approach, Microsoft kept the OLE name, dubbing it OLE version 2.0.
With the COM communication architecture, OLE 2.0 was able to connect objects outside the process boundaries of an application and could instantly support new versions of objects without changing the source code of the applications that used them. This approach connected binary components actively running in various process spaces. To differentiate this new technology, Microsoft stopped proclaiming that, "OLE stands for Object Linking and Embedding" and declared that OLE was no longer an abbreviation, the word "OLE" was the entire name for the technology. OLE is still spelled out with all capital letters, but is commonly pronounced "Oh-LAY" instead of "Oh-el-E."
With the framework in place to create reusable designs independent of the implementations, any new features and technologies can be accommodated by COM and OLE 2.0 architecture without changing the architecture itself. Many subsequent developments have been built under the OLE banner, but because of the extensible nature of the OLE 2.0 architecture, there is theoretically no technical reason for Microsoft to develop a technology that could properly be named OLE 3.0. For that reason, any subsequent mention of OLE in this book without a version number will refer to the OLE 2.0 set of technologies.
Visual Basic advanced from version 3 to version 4, and the 16-bit VBX components were succeeded by OLE-based 32-bit components called OLE Controls. Naturally, OLE Controls needed a new name to distinguish them from their 16-bit VBX predecessors. Microsoft assigned OLE Controls with the .OCX filename extension, and these components became known as "OCXs." OCX components provided modular, 32-bit functionality to all of the popular visual programming languages, including Microsoft Visual C++ and Microsoft Visual Basic. OCXs are full-fledged COM objects.
OLE Controls was one member of a whole family of COM-based technologies renamed under the OLE banner. Here are the highlights:
The confusion over the OLE names was beginning to diminish, just about the same time Microsoft realized that the I-net was a much bigger phenomenon than it had anticipated. Microsoft decided to realign its naming conventions once again and return OLE to its roots. The OLE banner was officially removed from all but the original set of technologies related to the linking and embedding of objects into OLE container documents. These three technologies are now grouped into a category called OLE Documents and are individually named Linking, Embedding, and In-Place Activation.
In 1995, the Internet revolution spurred Microsoft's technical (and marketing) ingenuity into high gear. Microsoft originally underestimated the importance of the Internet revolution, but within about a 90-day period in late 1995, Bill Gates completely realigned the Microsoft corporate strategy to include Internet support into almost everything they were producing.
To boldly signal their entry into the booming I-net revolution, Microsoft created a new high-tech "Active" brand name for present and future I-Net-related technologies. They decided to market the remaining OLE technologies as "ActiveX," which is easier than OLE to pronounce but much harder to define. Because ActiveX is essentially a brand name, expect the collection of technologies in this category to mutate over time. Microsoft further marked their Internet intentions by declaring their new Internet architecture for the PC would be named the "Active Platform." At present, the following technologies are included under the Active Platform umbrella:
All of the ActiveX technologies are all built to use COM. However, not all COM-based technologies fit under the ActiveX umbrella. For example, MS Office software and MS Windows operating systems are COM-enabled, but are not considered part of ActiveX.
Today there is probably more confusion than ever among developers over the various names for Microsoft produced technologies. At this point, understanding the underlying concepts is far more important than keeping up with the current naming conventions. If you are curious what the names were in late 1997 when this chapter was written, Figure 23.8 diagrams the relationship of some of these OLE/ActiveX technologies to the COM foundation.
FIG. 23.8
The ActiveX and OLE technologies all have a common foundation in COM.
The proud tradition of naming confusion with OLE Controls was continued in March of 1996, when Microsoft used the Active brand to rename these components as "ActiveX Controls," enabled them to operate as COM-compliant components, and dedicated their use to the Internet. Sure, you can still use these controls as widgets in the design-time toolbox of your development language, but now all the promotion and commotion revolves around using ActiveX Controls to make spectacular Web pages and Web-enabled applications.
Microsoft's Web-centric focus also spurred its aggressive push to develop or enhance their other Internet-related technologies. These products and technologies included Active Server Pages, VBScript, Visual J++, and Visual InterDev. COM will continue to provide the underlying architecture designed to integrate all this new technology seamlessly.
For objects to interact between computers and across networks, they must have a common way of communicating. This is accomplished by using an Object Request Broker, or ORB, which can be thought of as a very intelligent switchboard providing directory and connection services between client and server objects. A variety of ORBs and ORB-like technologies are currently available, and there has been considerable debate over which one is, or should be, the universal standard.
One early entry in this technology category was the Distributed Computing Environment (DCE) from a technology consortium called the Open Group. DCE encompasses an evolving suite of technologies available for a variety of platforms. A wide array of system vendors distributed this DCE technology, including Bull S. A., Cray Research, Data General, Digital, Fujitsu, Hewlett-Packard, Hitachi, IBM, NEC, Siemens Nixdorf, Sony Silicon Graphics, Stratus, and Tandem Computers. Various DCE technologies have been used as a foundation for other technologies released by IBM, DEC, and, yes, even Microsoft.
Another development was the Common Object Request Broker Architecture (CORBA) from the Object Management Group (OMG). The CORBA specification was developed in the early 1990s and has now gained the support of over 20 major technology vendors, including Apple, Sun, and IBM. OMG has achieved a very impressive membership of over 700 companies, including Adobe, Apple, Computer Associates, Digital Equipment, Hewlett-Packard, Netscape, Novell, Oracle, Silicon Graphics, Symantec, Texas Instruments, Unisys, and Xerox. Despite this apparently overwhelming show of support, OMG will not be able to ensure CORBA's status as the single industry standard without the cooperation of the most globally influential company in software today, Microsoft. Unfortunately for OMG, CORBA compliance does not seem to fit into Microsoft's plans.
http://www.omg.org For more history about CORBA and the Object Management Group, visit the OMG Web site.
Back at Redmond, Microsoft diligently created its own full-fledged object request broker specifically for the Windows set of operating systems. After years of development rumors, this technology was officially released in 1996 as the Distributed Component Object Model, or DCOM. DCOM extended the COM architecture to provide object communication services at the binary level between computers that use a COM-compatible operating system and are connected via a network. For general discussions about the technology, the term COM is commonly used to denote both the COM and DCOM technologies.
COM and DCOM deliver much functionality, but Microsoft wants to make COM even better. Microsoft is working on a unified and improved version of COM and DCOM called COM 3.0, which is due to be released with the next major update of Windows NT. Rumors suggest that COM 3.0 will provide faster performance and a more versatile inheritance capability, but we will have to wait and see. With this announcement, Microsoft has made their intentions clear that both now and in the future, the road to Windows-based, object-oriented software is paved with COM.
COM is the Microsoft way to connect objects, but it isn't the only way. The industry is presently grappling with the choice of designing programs to communicate using a designated open standard (such as CORBA) or communicate using COM, the emerging object communication technology that is the de facto standard for Windows-based computing.
CORBA-based technologies are the most significant competing object models you will encounter when supporting enterprise-wide integration of your programs created with Visual Basic.
One CORBA compliant object architecture that is available for mainframe systems is the System Object Model (SOM) and Distributed System Object Model (DSOM) from IBM. If your company has IBM-supported mainframe or workstation equipment, you probably want to become more familiar with SOM/DSOM.
http://www.ibm.com For more information about SOM and DSOM, visit the IBM homepage and perform a site search for SOM or DSOM.
Other popular CORBA-based ORB offerings are available from Visigenic and IONA. Both companies have also created technologies that help you integrate COM and CORBA objects, and both will be discussed further in the following section.
CORBA-based solutions are still the main competitors to COM. CORBA has been available much longer and was created from the start to be a non-proprietary or "open" standard, backed by OMG and its alliance of technology firms. COM was created as a proprietary Microsoft development. During October of 1996, in an effort to diffuse concerns about it being proprietary, Microsoft announced the creation of "The Active Group" to guide the evolution of ActiveX and the underlying COM technology. The Active Group was formed under the auspices of The Open Group, which is another organization dedicated to supporting standards in the software industry.
http://www.opengroup.org For more information about The Open Group, visit their Web site.
Although this appeared to be a positive step toward opening the architecture to the rest of the industry, the track record suggests that Microsoft will retain the deciding vote on any significant changes proposed for COM.
The industry track record also indicates, however, that Microsoft is an extremely tough contender in any market it enters. Microsoft can be counted on to be particularly aggressive in promoting and implementing its object technology, because this area is absolutely critical to the advancement of personal computing. In cooperation with Microsoft, Software AG has ported COM and ActiveX support to the Sun Solaris and UNIX family of operating systems with a product called DCOM for the Enterprise (DCOM FTE). Microsoft is working diligently to ensure that every major operating system will soon have COM support. Hence, using Visual Basic to create COM-compliant objects for Windows and UNIX platforms is a safe bet now, and COM objects will have even more utility in the future as COM support spreads to other platforms.
http://www.sagus.com For more information on COM and ActiveX support for UNIX-based operating systems, visit Software AG's Web site.
There is still a great debate over which object model is best to use: COM or CORBA. Despite all the hype, choices must be based on what is better in a given context. If you're creating applications for the PC desktop, the Object Request Broker de facto standard for Windows-based development is COM, and COM is built right into the operating system. Similarly, COM is the clear choice for homogeneous Windows-based programs used across Windows NT-based computer networks. If, however, your programs need to operate in a network environment that contains a mix of operating systems, including various flavors of UNIX, you might need to integrate your programs by using CORBA-compliant ORB software. Other ORBs may find niche markets where they can survive, but for enterprises where most users have PCs on their desks, the initial stand-off between COM and CORBA will likely give way to integration and then finally to assimilation by COM.
http://www.omg.org/new/corbkmk.htm Object Management Group maintains a concise set of links to CORBA-related web sites.
Perhaps you are wondering if programs created in Visual Basic (which uses COM) are compatible with CORBA. Such integration is not yet available out of the box, but there are now third-party tools to help you with this integration.
Visigenic is a growing provider of CORBA-compliant technology, and has licensed its VisiBroker ORB product to an increasing number of companies, including Netscape, Novell, Oracle, Sybase, and Silicon Graphics. Visigenic also offers a product called VisiBridge, which provides technology that enables COM objects on Windows platforms to communicate with CORBA objects on UNIX and mainframe operating systems.
http://www.visigenic.com For more information on VisiBroker, VisiBridge, and other Visigenic products, visit their Web site.
Another popular CORBA-compliant object broker is Orbix from Iona. Orbix also provides a bridging technology that allows application integration between COM-based Windows platforms and CORBA-based UNIX platforms.
http://www.iona.com For more on Orbix and other Iona products, visit their Web site.
These and other products to bridge the compatibility gap between COM and CORBA are very useful, and will be necessary until the two competing standards can either communicate directly, or are merged into a unified standard. Bridging technology provides valuable integration capability, but unfortunately, as you increase the number of communication layers, the complexity of your programs and the risk for functionality and performance problems also increases. Because COM is quickly being ported to nearly every major computing platform, it might not be long before native COM support is available in all common platforms and Visual Basic programmers will have much less need for a COM-to-CORBA interface layer.
The battle between CORBA and COM to become the generally accepted ORB standard is nowhere near over. etscape Communications Corporation is including a CORBA-compliant object request broker and support for the Internet Inter-Orb Protocol (IIOP)--the CORBA-compliant Internet protocol from OMG--in their Enterprise 3.0 server and Communicator 4.0 browser. This means Netscape's browsers can use IIOP to connect with remote objects across the I-net. The incompatibility gap between Microsoft's Internet Explorer and Netscape avigator/Communicator is widening, but Microsoft is trying to make this a moot point by using Active Desktop as a preemptive strike to essentially eliminate the need for--and ultimately even the existence of--all competing browsers. Microsoft's Active Desktop concept integrates all the functions of traditional browser technology directly into the desktop of all future Windows operating systems. Hence, if the PC using public embraces this new version of Windows, the PC browser battle will essentially be over and Microsoft's COM-based technologies will be the de facto standard.
http://www.microsoft.com/oledev Microsoft maintains a Web page dedicated to OLE and COM topics. You can find a wealth of additional information there.
COM/OLE versus OpenDoc
Assimilation has also begun under the market pressure of the COM/OLE technologies. In late 1993, a coalition of technology companies--including Apple, IBM, Novell, Oracle, SunSoft, Taligent, and Xerox--backed a compound document architecture standard called OpenDoc from Component Integration Laboratories (CILabs). This technology provided some promising competition for Microsoft's COM/OLE technology and prompted a great deal of interest and discussion from the industry, but generated only a meager amount of market share. Being both "open" and "standard" was just not enough for the OpenDoc alliance to overcome the growing market success of COM-based technologies. OpenDoc's battle to survive amid the growing dominance of OLE technologies was short lived. The battle concluded in May of 1997, when CILabs essentially surrendered by announcing they were discontinuing support of the OpenDoc architecture.CILabs has since been dissolved by its board of directors. At the date of this writing, their farewell greetings were posted at the following Web address: http://www.cilabs.com.
Still another battleground for Microsoft's object technologies is in the I-net (Internet and intranet) arena. This battle has become more intense as a result of the explosive increase in I-net usage and the increasing desire to use I-net for mission-critical, distributed corporate applications.
Sun Microsystems has created a phenomenon of its own with the creation of the Java programming language. Originally created to be a small but powerful means to program applications for small computing devices, it found popularity as a means to create quickly downloadable applets for I-net Web pages. As Java's popularity expanded, so has the scope of what developers are attempting to create using Java and Java related technologies.
Microsoft's mixed position on this situation is easily misunderstood. Microsoft considers Java to be a great object-oriented programming language (and has delivered Visual J++ to provide Java programmers a rich development environment). However, Microsoft considers Java related technologies to be a very poor choice as an operating system. On all but the smallest computing platforms, the Java Virtual Machine (JVM) that contains and executes the Java applets is essentially an operating system built on top of the native operating system.
Since Java and Java-related technologies are attempting to be a cross-platform solution, the Java language and the JVM must limit itself to the lowest common denominator in terms of platform specific features. Thus, important capabilities available from operating systems (such as Windows) are essentially unavailable to programmers writing "Pure" Java code. The platform-specific implementations of the JVM also introduce the opportunity for incompatibilities, as does the variety of Java development environments. Even if you think you have developed the most "pure" Java applets or even full-fledged Java applications, you should still test them extensively to be sure they work correctly on all desired platforms.
JavaBeans is another Java-related technology that is not popular with Microsoft. JavaBeans is a technology that Sun and IBM have developed to give Java applications the same compound document capabilities that ActiveX provides. JavaBeans can be visual components that you can add to forms in visual development tools, or they can be non-visual components that accomplish background tasks. JavaBeans are also designed to be cross-platform compatible, but the current reality is that you still need to test your Java and your JavaBeans on each combination of platform and Java Virtual Machine that is represented in your user community.
Looking further into Java's relationship to the I-net, Sun actively supports the CORBA specification and has pledged to integrate Java with IIOP, the CORBA-compliant Internet protocol from OMG. Java's existing Remote Method Invocation (RMI) protocol will be integrated to use IIOP across the I-net. Hence, Sun has joined other industry powerhouses such as Netscape, Oracle, and IBM in support of IIOP. Despite such a powerful alliance among giants in the industry, they can still be considered underdogs when compared to Microsoft and its rapidly growing ActiveX/COM/DCOM strategy for I-net development.
Microsoft is hoping that as COM gains further acceptance and use throughout the industry, the importance of CORBA and other architectures will diminish along with their market share. If that happens quickly, Microsoft will have assimilated another major layer of the desktop computing architecture. If not, CORBA will remain as a viable, competing distributed object architecture, both for the desktop and the I-net, and will need to be accounted for when you are integrating and supporting enterprise-wide systems.
http://www.microsoft.com Microsoft provides a variety of information about their positions on these technologies. Simply do a keyword search at their Web site.
http://www.omg.org A good starting point to locate more information on CORBA is the Object Management Group's Web site.
Although COM is currently a Windows-based technology, Microsoft and its partners are porting it to every other major computing platform and operating system, including Solaris, MVS, Macintosh, and UNIX. This time the integration rumors are backed by dollars, because in addition to their internal efforts, Microsoft has negotiated outside contracts to port COM and DCOM to some of these other platforms, including UNIX-based machines. As was mentioned previously, COM is native to Windows, and Software AG has already ported COM and ActiveX to Solaris and the UNIX family of operating systems. If COM is not yet supported on your computer system of preference, you probably won't have to wait long until it is.
While still maintaining language neutrality, take a closer look at the common implementation requirements of Microsoft's object technologies. This section provides a quick introduction to COM-related terms and concepts, explained in greater depth in subsequent chapters with language-specific implementation guidance.
The COM infrastructure is built to support communication through object interfaces. In COM, an interface can be thought of as the communications link between two different objects, and the set of functions available through that link. Interface names conventionally begin with a capital I to denote their status as an interface, and the remaining text describes the function or service being exposed. For example, in the Domestic Simulator program mentioned earlier, you might name an interface "ISpeak," which would be read as either "I-Speak" or "Interface Speak."
A COM interface is actually implemented as a memory structure called a VTable, which contains an array of function pointers. Each element of the VTable array contains the address of a specific function implemented by the object. It is conventional to say that a COM object exposes its interfaces to make its functions available to clients. The object also can contain the data manipulated by the methods, and you can choose to keep this data hidden from the client object. This structure is illustrated in Figure 23.9.
Unknown One of the most basic rules of the COM specification is that all COM objects must support a specific interface named IUnknown. The IUnknown interface is the standard starting point for referencing any other interface that the COM object might contain. The COM specification arbitrarily dictates this structure, but it makes sense if you consider that a client object doesn't know what other interfaces are exposed by a server object until the client requests this information using the predefined interface designed to reveal what interfaces are "Unknown." A more technical way of stating the relationship is that an object's interfaces must either directly or indirectly inherit from IUnknown to be valid under the COM specification. You, the programmer, are responsible for implementing the IUnknown interface for your objects; however, because this is such a routine operation, Microsoft programming tools either accomplish most of this work in the background or allow you to customize some basic example code. Figure 23.9 also shows how a client must request a pointer from Iunknown to access an object's interfaces.
FIG. 23.9
You could define a COM interface called ISample, which is implemented in memory
with pointers to the standard COM methods and to your custom-defined methods.
QueryInterface The object's IUnknown interface must also implement a function named QueryInterface, which reports back to the client whether a requested interface is supported and, if so, provides a means to access it. A successful call to QueryInterface provides the client with a pointer to the requested interface.
Every COM component class and interface must be uniquely identified. This is accomplished by providing a means for you, the programmer, to generate and assign a unique number called the Globally Unique Identifier (GUID). A GUID (pronounced "goo-id") is a 128-bit integer virtually guaranteed to be unique across space and time until about 3400 AD, because of the algorithm used to create it. To create a new GUID, the algorithm uses the current date and time, a clock sequence, an incremented counter, and the IEEE machine identifier. The odds against any two people ever creating the same GUID are astronomical. After you have created a GUID, you can used it to identify your programming objects and interfaces uniquely. When used to identify an interface, this GUID number is referred to as an Interface Identifier (IID). When used to identify an object, the GUID number is called a Class Identifier (CLSID).
After a particular interface has been defined, numbered with a unique IID, and published, it must not be changed; thus the interface is said to be immutable. When you want to update the features of an interface, you must define, number, and publish an additional interface to supplement the older version, retaining any previous versions within the component. If you don't include these previous versions, you will create version incompatibility problems for your users. You will see an example of this presented in the section on version control.
After you have a GUID to identify your new object, you must register it with the host system. For machines running Windows, you do this by adding the appropriate information to the Windows Registry, a special system file containing that machine's hardware and software configuration information. When you create and distribute components, you should build your installation program to update the Registry without requiring manual assistance from the user. After a component is registered, the operating system will know how and where to access that particular object.
NOTE: According to the current definition, any self-registering OLE component that fully implements IUnknown can be correctly called an ActiveX control. This means that both OLE Automation servers and most of the widget-type controls from a visual development toolbox are included under the ActiveX controls banner.
With the ability for developers around the globe to create objects that must work together, you need to be able to update your objects without causing the failure of existing objects that still depend on your previous version. COM requires that client objects specify the exact server interface they want by using that interface's assigned globally unique identifier (described further in the next section). Each version must have a different identifier, and QueryInterface returns a pointer to version the client specifically asks for. COM enables objects to have multiple interfaces, so any number of versions can be supported simultaneously. When all versions are retained by an object and made available to clients, both the old and new clients always work appropriately. When you create a new version of an interface, it is a good practice to change the name as well to avoid confusion. One simple convention is to add a version number to the end of the existing interface name, incrementing it for each new version. With this versioning process, you can safely support both old and new objects on either side of the client-server relationship. Figure 23.10 illustrates this concept, using objects from the Domestic Simulator example.
FIG. 23.10
Mower objects from different vendors will still get the grass cut as long
as COM versioning rules are followed.
Figure 23.10 depicts the following situation of the COM versioning rules being followed:
Suppose that purchased the Domestic Simulator described earlier. Spouse95 objects created by SimWare are designed to call the PushMower interface (IpushMower) in the Mower95 object when the simulated lawn needs to be cut. Eventually, LawnSoft releases a new version of their mower object called Mower97 that is the envy of the simulated neighborhood. Some people buy it to upgrade their simulation, but since it's more expensive and not that much better than the mower object everybody started with, most don't buy it. Furthermore, for your simulator to take advantage of this new type of mower, your Spouse object must know that the new mower exists and must know the identifier of the new interface to access it. To enable spouses to use this new variety of mowers, SimWare upgrades their Spouse object and releases the Spouse97 version. The Spouse97 object now has two ways to mow the lawn: use the MowLawn object to call the old PushMower interface, or use the new MowBigLawn object to call the new RideMower interface.
The whole point of this exercise is that since the versioning was done correctly and each object supported prior interfaces, any combination of spouse and mower objects was able to successfully mow the lawn. If not, when you drop in a replacement object, you risk breaking the existing object relationships and your application no longer functions correctly. Make it a habit to ensure that your objects fully support your prior interface definitions as well as any new interface definitions in a given object, and your objects will always work together properly.
A client can create a new instance of an object by making an appropriate call to the object creation services provided by the COM Library. How this is accomplished depends on the language, but the call always needs to provide the GUID for the class identifier (CLSID) of the desired object.
At the completion of the creation process, the client gets a memory pointer to the new object. It can then use that pointer to ask the object directly for pointers to any other services provided by the object.
Each platform that supports COM provides a COM Library. The COM Library implements a group of functions that supply basic COM services to objects and their clients. The first and foremost service is the means to create a COM object upon request. One way to accomplish this occurs as follows:
If you want to create more than one object at a time, you could repeat the previous creation process, but a more efficient way is to implement and use a class factory. A class factory is a service component whose only purpose is to create new components of a particular type, as defined by a single CLSID. The standard interface used for this purpose is appropriately called IClassFactory. It is up to the component programmer to provide the class factory for each class but, fortunately, implementing the IClassFactory interface is simple and straightforward. If you want to add licensing capabilities to your class factory, you can opt to use Microsoft's newly defined IClassFactory2 interface, which requires the client object to pass the correct key or license to the class factory before it will create the new instance of the desired class.
Another way to accomplish the creation of an object is by using a moniker. A moniker is a special type of COM object built to know how and where to instantiate another specific object and to initialize that object with its persistent data. Each moniker can identify exactly one instance of an object. If you want more than one instance of a given class of objects, you need to use a different moniker for each object, because each object might have its own unique data. For example, suppose that in the human resources application at your firm, employees and their histories are stored as COM objects. If you use monikers, a separate moniker would be needed for each employee in the company. If your employee object, for example, were needed by the system, the system would call up your particular moniker. Your moniker would then create your employee object in memory, load your history information from the persistent data storage, hand the pointer for the employee object back to the requester, and then unload itself from memory.
You can also create a composite moniker that activates a group of other monikers. Absolute monikers point to OLE documents instead of objects.
After it is created, a COM object requires a place in memory where it can exist, deliver the services requested by clients, and then unload itself from memory when all the clients report that they are finished with it. To have this existence, a computer object must live within a process or process space with a defined area in the system memory, some instruction code, perhaps some associated data, and some resources for interacting with the system. A thread is the name given to the action of serially executing a specific set of machine code instructions within a particular process space. Computers with processors and operating systems that can execute more than one thread in each process are said to be multi-threaded. A process capable of multi-threading must always have at least one main thread, called the primary thread, but can also have many others. In Windows, user-interface threads are associated with each window and have message loops that keep the screen display active and responsive to new user input. Meanwhile, Windows uses worker threads to accomplish other computing tasks in the background. After a thread is initiated, it executes its code until it finishes, is terminated by the system, or is interrupted by a thread with higher priority.
COM supports multi-th reading by putting objects in the same process space into their own groups, referred to as apartments. The purpose and function of apartment threads are comparable to those of the user-interface threads described above. Similarly, COM uses the term free threads to describe what were previously called worker threads. Regardless of the terminology, COM makes multi-threaded development easier by handling the communication between these threads and between the various objects.
COM objects must be designed to be well-behaved neighbors. You want to ensure that any COM objects you create correctly implement the rules that enable consistent behavior and reliable communication with other objects. Following are the most basic rules your COM objects must live by.
After an object is created, it can take on a life of its own. Because more than one client might be using its services at any one time, each object needs to keep track of its clients so that it doesn't close itself down before all the clients are finished with it. When a client begins using the services of an object, it has the responsibility to call the AddRef method to increment the server object's reference counter. Similarly, when the client has finished, it has the responsibility to notify the server object by calling the Release method to decrem e n t the reference counter. When all clients have released themselves from the object, the reference counter goes to zero. The object then knows its work is completed and can safely save any persistent data and self-destruct by unloading itself from memory. If a client subsequently needs the object, the object is created again and the reference counting process is repeated.
You must implement the methods for AddRef and Release as part of the IUnknown interface. Because all interfaces inherit from IUnknown, the AddRef and Release methods are then automatically available through any interfaces you define for your object.
COM objects eed to be able to communicate with their local neighbors on the same machine, as well with distant COM objects residing on machines located on the other side of the world. There is considerably more complexity involved with the latter process, but you need to understand both processes to create objects that comply with the COM specification.
Remote COM Servers Remote COM servers (also known as cross-process or out-of-process servers) are COM objects providing service from a physically separate computer that is usually connected via a networ k . Both computers must be operating with COM-enabled operating systems for this process to work correctly. Remote COM servers typically provide slower performance than their in-process cousins, but they can deliver all the advantages explained earlier for generic OOP remote servers, and COM remote servers can provide compatibility between 16-bit and 32-bit clients.
Transparent Connections: Marshaling, Proxies, and Stubs In COM, a proxy is a small binary component activated in the client's process space, which acts as an in-process connector to the server interface, regardless of the server's physical location. A stub is a small binary component activated in the server's process space, which acts as an in-process connector to the proxy in the client (see Figure 23.11). With this arrangement, the COM client doesn't need to know where the server object is located, because COM insulates by creating a proxy or stub as needed, making all servers appear to be in the same process space.
FIG. 23.11
COM proxies and stubs provide each remote objects with an in-process communication
link to other objects across a network.
While a great deal more overhead is necessary to communicate with out-of-process objects than in-process objects, no additional effort is necessary for the client. With this architecture, all objects are made available to clients in a uniform, transparent fashion.
Some extra work needs to happen behind the scenes to make this communication process transparent to the objects. For an in-process server, the client can simply use a pointer to the server, but out-of-process clients only have a pointer to the proxy, and COM must support inter-process communication such as Remote Procedure Calls (RPC) to reach the stub, which then communicates through a pointer to reach the server. Marshaling is the name given to this process of packaging interface data into an appropriate format for delivery across process or network boundaries. The code module that performs these tasks is called a marshaler. For the return trip back to the client, the process to unpackage this data is called unmarshaling.
Object Automation If you want to create an object that can be programmed or automated by another object, your object will expose this capability through a specially defined standard interface called IDispatch. The IDispatch interface, also called a dispatch interface or Dispinterface, must implement a standard method called Invoke that acts as a channel to receive automation commands from clients. Dispatch interfaces enable you to expose functions and data as methods and properties, which are combined in logical units called automation objects. An application or component that exposes automation objects is called an OLE automation server. An application or component that uses the services of an automation server is called an OLE automation client. The IDispatch interface is used by OLE Automation servers and is generic across programming languages.
COM objects, much like traditional applications, need to be able to store their data. A COM object can store its data in the following ways:
A COM object that can store its data by using a fairly permanent medium (such as a disk drive) is said to have persistent data. Persistent storage is accomplished in COM by means of a structure formerly called OLE structured storage and now called Compound Storage. This structure essentially implements its own independent file system, and the whole thing is stored within a single traditional file on the host machine.
In comparison to the traditional file structures, the directories are called storages, and the data is stored in file-like structures called streams. In much the same way that files contain data specially formatted for the program that created it, streams can hold data in any format designated by the object that creates it. The Root Storage can contain any number of additional storages, also called substorages, and any number of streams. Each substorage can also contain any number of additional substorages and streams, with the only limit being the amount of space available on the disk. This entire structure is then physically stored as a single conventional file on the system's disk drive. The familiar DOS/Windows file structure and the COM Compound Storage system are compared in Figure 23.12.
FIG. 23.12
The COM Structured Storage architecture closely resembles the DOS/Windows
file system architecture, but is stored on a single file on the host system.
Historically, there have been many ways to exchange data between programs. You could import a data file or copy and paste from the Clipboard, or perhaps a program might read an initialization file. COM has defined a common approach for moving data between objects called Uniform Data Transfer. Again, a standard interface is defined to accomplish this functionality, and it is called IDataObject. By calling the IDataObject interface, one COM object (an ActiveX control, for instance) can quickly and easily request data from another object any time new data is needed.
When one object needs data from another object based on data changes, timers, or other spontaneous events, a better technology called Connectable Objects can be used to pass the data between objects. The idea behind connectable objects is to set up dedicated incoming and outgoing interfaces used exclusively for inter-object communication. Each connection is instantiated with its own Connection Point object within the server object. The client instantiates an internal object called a sink, which receives the incoming communication. Standard interfaces defined for this purpose are named IConnectionPointContainer and IConnectionPoint.
For most large organizations, the issue is no longer whether your firm should move to client-server, object-oriented, and distributed computing models, but when and how.
Most organizations would like to begin this transition immediately, especially for any new applications, since the benefits of building applications by using object technology are compelling. However, creating new OOP systems of significant size and complexity typically requires additional training for the programming staff, a large investment of funds, and months or years to complete. During that time, all the existing applications built with traditional methods still need to be maintained and updated. Furthermore, new and old systems will usually have to interoperate during the transition, since instant transitions involving multiple systems are rarely possible. This is indeed a daunting task, but the sooner your organization determines their strategy for making the transition, the better prepared you can be for implementing it.
Once committed to begin the transition, the issue is then how you can economically and sensibly accomplish it. Because aging systems still provide a great deal of functionality, and changing them is getting more expensive every day, many businesses have focused on more urgent problems and secretly hoped for some technical breakthrough to come along and save them before it's too late. Object technologies are no magic bullet, but they can help you solve many of the challenges in creating new systems and updating old ones while maximizing your existing investment in your company's legacy code base.
For most large companies it isn't wise, practical, or even possible to simply discard the existing mission-critical applications and quickly rebuild them, using object technologies. If this is your situation, you need an incremental strategy that allows you to selectively redevelop, reengineer, and repackage your legacy systems, blending the old processing models with new ones that can all share the same communication framework. COM object technologies can provide that framework.
Each corporate situation presents unique challenges that must be addressed individually, but when dealing with these challenges, some common themes emerge. Although there are many variations and combinations, this section explores four basic strategies for applying object technology to the challenge of supporting your legacy computer systems:
There are many mission-critical legacy systems that are stable, reliably do exactly what they are supposed to, and require very little maintenance. Perhaps this claim sounds amazing, but these are the applications that allow your business to continue another day while you and your teammates diligently resolve the most recent crisis or create that "rush job" application desperately required by your more vocal users.
Sometimes there is a compelling reason to change the status quo: the business process changes dramatically, the cost for support becomes unacceptable, the program could no longer be supported if a serious modification were required or if the language or platform were no longer supported by your company. In such a case, you will want to start planning the transition and focus on the new system. Because this process will require a great deal of your already scarce programming resources, why not hedge your bets and make use of the legacy system as long as it is practical?
Situations involving this approach would include:
Another approach is to take existing code modules and imbed them inside a new object that acts as a wrapper, completely isolating the legacy code while retaining its functions. This wrapper can encapsulate any or all of the old interfaces, including screen displays, API calls, database interactions, and any other communication elements, exposing them as appropriate COM interfaces. As time, funding, and priorities permit, you can then progressively design and implement new objects to replace the wrapped modules from the legacy application.
There are several specific ways to use this wrapper approach. A database wrapper accesses and encapsulates just the legacy data, completely bypassing legacy code. A service wrapper encapsulates legacy system services, such as printers and communications devices. An application wrapper encapsulates both the code and data of a legacy system application. Application wrappers can provide the object-oriented equivalent of traditional "screen-scraping" programs, which emulate the interaction of a user on a character-based terminal and provide that data through a new graphical user interface. Although Visual C++ is probably the most common choice for creating these wrappers, you can also use Visual Basic.
Although wrappers can be a fantastic method for leveraging your legacy programming investments, this approach has a major downside because the old, inflexible legacy code is still hiding inside the object wrapper. The effort to replace the wrapped legacy code with a newly constructed, reverse-engineered object is likely going to be seen as a very low priority among the users, because they have already received the new functionality and now feel comfortable with it. From their point of view, maintaining the new functionality is your problem, not their problem. Replacing a functioning wrapper object might also be a tough sell to management, who probably have a huge list of higher priority new initiatives and maintenance projects that need your immediate attention. With this in mind, it is usually wise to build object wrappers with great care and attention to detail, as they might need to last until the underlying function is obsolete to the business, or until a completely new system replaces it.
Functions performed by existing monolithic architectures can be reengineered or broken down into a multi-tiered, object-oriented architecture that is more maintainable, scalable, flexible, and reusable.
The first step is to divide functions into three logical groups representing user services, business services, and data services. This analysis can be more challenging than it sounds.
The user services group includes all the functions that support interaction with the user, such as forms, menus, controls, and other visual displays. User interfaces on the old system might be simple text-based screens, whereas the new system probably needs a considerably more elaborate graphical user interface.
The business services group includes the functions that implement the business tasks and rules of the organization. If your company is typical, many of the business rules are undocumented, yet still buried deep within the legacy applications, and must be derived by analyzing procedures and even individual lines of legacy code. When you have discovered them, the business rules contained in the old system must be compared to the rules you want for the new system and adjusted accordingly.
The data services group includes all the functions related to the storage and manipulation of data. Legacy data storage might be implemented with a flat-file data structure, whereas the new system might need to use a relational database.
This logical design might need to be revised as the business needs change, but after the initial implementation is accomplished, subsequent iterations of this cycle are much faster and easier. After this logical division of tasks is accomplished, you can design the physical location of the components.
In most organizations, the legacy system cannot be shut down while the new system is under construction. Where possible, use an incremental development process to allow the programming team to deliver a series of small victories instead of an ever-more-anxious period of waiting (and hoping) for the success of one enormous project release.
The first components to build are the ones that support the lowest level functions of the system. Resist the urge to start with the user interface, because you will quickly become frustrated trying to design interfaces for functions that are not yet implemented. These low-level functions include such things as data access components, networking services, and services for hardware and peripherals. Because these functions are potentially needed by any application on the system, migration of these services to reusable components will provide the fastest benefit for other projects that also require these services.
The next components to build are those that support time-consuming computer processes that can be more efficiently accomplished in a separate process space. The main system's performance can be greatly enhanced by implementing these slower processes as components that can operate in a separate process on the same machine or on a separate, more specialized machine. Functions that fit this category might include fax processing, database report generation, credit card validation, and computing of numeric solutions by using complex algorithms. When these components are moved onto a separate processor, asynchronous processing can be utilized by any application on the system modified to take advantage of it.
After these two groups of components are built, tested, and operational, you have formed the foundation that can support a cycle of continuous improvement. Each of the following projects should provide the remaining components to one major subsystem of the legacy application. Attempting to complete all the remaining components at one time is a risky endeavor that should be avoided if possible for two reasons. First, the users and management of most organizations like to see steady progress for all the money they are investing in your programming projects. The smaller the scope of the project, the less likely you are to encounter problems that require significant schedule delays. Second, because this technology is still relatively new, it is likely that your programming team will be gaining valuable experience during this process, and schedules are rarely built to accommodate many "experience-building" mistakes. By taking the path of incremental improvement, your team can gain experience as they deliver a steady stream of system enhancement on time and on budget.
One of the fastest and most exciting approaches capitalizes on the recent explosion in the use of I-net technology. You can create object-oriented interfaces for your legacy data by porting the functionality of your legacy applications to a browser-based implementation. The larger your organization, the more promising the benefits, as long as your company already has the equipment and infrastructure in place to facilitate it. If access to Local Area Networks, Wide Area Networks, and the Internet from each desktop is commonplace at your organization, this might be your fastest, least expensive, and most flexible option for transitioning away from your legacy systems.
If your company does not yet have the infrastructure in place, the benefits of this approach can be so compelling as to become a cost-justifiable reason to start investing in the infrastructure.
This I-net approach relies on the browser on each user's desktop to act as the client container for your interfaces and data. The browser and an I-net connection to the company I-net server computers are the only additions required at the user's computer. With this approach, as soon as you change the content delivered by the central server computer, everyone can use the most recent version.
For an example, suppose that your firm's existing inventory database resides on a legacy mainframe computer and is accessed from PCs by using a terminal emulator. Replacement with new browser-based graphical user interfaces can be implemented by incrementally creating active server pages to suit the needs of each user group (sales, order fulfillment, finance, and so on). These pages can be delivered from an internal I-net server that was also connected across the network to the legacy mainframe. COM objects created to run on the server provide services by making database calls to the legacy database, and delivering the results to the objects operating in the user's browser container. Both the legacy system and your net I-Net system can operate simultaneously until all the user groups were provided new interfaces. When the legacy database is migrated to a new system, the calls to the legacy database can be modified and redirected to any standard Structure Query Language (SQL) enterprise database product. If your database product is COM-compatible (such as Microsoft's SQL Server), your task is even easier.
The benefits of this I-net approach can be downright exciting to an IS department trying to support a large user base. Gone are the hassles of software version control, trying to physically install or upgrade individual copies of your corporate software on hundreds or thousands of geographically separated computers, or trying to use network install utilities to accomplish these tasks. You can at least expect the following benefits from this I-net approach:
The list of benefits goes on, but even these few things are enough to illustrate the advantages to large organizations with a geographically dispersed user base.
Although this approach has numerous benefits, it does have some significant drawbacks that need to be considered before you commit to it. As with any centralized system, if the network goes down, the entire user community might suddenly lose their service. This can be somewhat mitigated by operating mirrored servers at separate locations, so if one server cannot be accessed by a given user, that user might be able to establish a connection to the alternate server. Even with mirrored servers, the fragile state of the global I-net communication backbone does pose a significant risk to mission-critical systems. If this risk is unacceptable, you need to stick with more traditional approaches.
Security is also a factor when considering any I-net based approach. Secure protocols are now available, but they have yet to gain universal trust. However, many corporate information systems can be operated safely with the minor degree of risk currently associated with I-net communication methods.
The first order of business for creating new applications is to gather and document as many of the project requirements as you can. This step cannot be overemphasized. Object-oriented programming allows a great deal of flexibility in designing and adjusting the solution to a given programming problem, but an entire project design might have to be scrapped if a critical requirement is omitted during design and then discovered during user testing. Use every resource at your disposal to ensure that you have captured all the requirements of the user's business process. Rapid prototyping is especially helpful when you're trying to present a new graphical user interface paradigm. By presenting a live demonstration of a sample graphical user interface option for a business process solution, you can often unlock creativity and innovation from the users during the design phase before formal development begins. This is a dual edged sword, however, as user expectations and requirements may also rise significantly. The risk is worth it, since you should be able to reveal and solve any existing problems with the business processes that are only rooted in limits imposed by a previous automation system. After you have documented a comprehensive set of business requirements, you can begin creating an object-oriented design.
When designing an application from scratch, the procedural programming strategy typically uses a mixture of two related approaches:
The suggested approach for OOP design draws on both the top-down and bottom-up approaches. First, examine your problem description from the top, and look for the items, descriptions, and actions described. The nouns are going to become the object classes, the adjectives are going to become the properties, and the verbs are going to become the methods. Then you can start the design process by establishing the classes and associating each method with the class that is most responsible for that action. From this starting point, you can concentrate on the bottom-level tasks, adding the properties and further refining your design. When your preliminary design is acceptable to you and your colleagues, repackage it as a presentation and walk through it with your most supportive user representatives. Even the most elegant object-oriented designs are worthless if they don't satisfy the needs of the users. Rapid prototyping is also a valuable tool for testing ideas, to ensure that the programmers and the users are communicating effectively and to reveal and facilitate the discussion of any hidden assumptions buried in the individual requirements.
When your most supportive users are happy, test a basic prototype with your least supportive users. Working with these users might not be comfortable, but it will likely yield two very important benefits. First, these more hostile users will tend to make a more determined effort to find the flaws in your project and your logic. Although some portion of this feedback might simply be frivolous griping, many of the comments will reveal places for significantly improving the project. Making peace with hostile users early in the process by negotiating these improvements into the initial design of the project is a much better strategy than avoiding these users and their opinions until you are forced to face them during full-scale user testing. The secondary benefit is that after these initially hostile users become part of the development process, they might join you in feeling some personal ownership in the project, subsequently defending the project to ensure "their" project becomes a success. Requirements definition and preliminary design are critical phases of the development process where changes can be made easily and cheaply, so don't waste the opportunity to capture as many changes as you can while fostering a positive spirit of support among the users.
After you and your users have agreed upon a preliminary object design, resist the urge to immediately begin coding. As a rule, it is cheaper and faster to buy an object than it is to write it yourself, so this is the time to start looking for pre-built objects. The first place to look is in your own organization. If your organization doesn't have a well-organized object repository, now is the time to start one. Next, look outside your organization for objects that can be obtained from other parts of the same company or from commercial sources. Remember that ActiveX components are COM objects, and there are thousands of commercially distributed ActiveX components to choose from. Only after you have truly exhausted your options for reuse should you pass out the coding assignments.
After your objects are developed, tested, and put into production, you need to determine who is going to support them. The project team that originally created the objects for a specific project will likely move on to other tasks, but the objects they put into the corporate inventory will (eventually) need maintenance. As many other projects reuse your objects, the responsibility for maintenance of a given object can become quite diluted. To avoid inadvertently abandoning these valuable objects, the objects themselves and the repository should be assigned to a project-neutral focal point or team. Perhaps some of the resources saved by reusing objects can be applied to the task of maintaining the object inventory, budgeted independently from the budget lines of the projects that created them. One final caution: Invest the time needed to prevent the nearly universal problem of poorly documented applications. Properly and fully document your objects, and then include that documentation and the objects in the corporate object repository. You may even need to reuse or maintain some of these objects yourself someday, so take the time to document them appropriately the first time around.
This chapter has provided an overview of Microsoft's object technologies and general issues involved with Object-Oriented Programming. Now that you have a general understanding of OOP and Microsoft's object technologies, its time to get some instructions on how to create a few of those versatile COM objects known as ActiveX components:
© Copyright, Macmillan Computer Publishing. All rights reserved.