
by Cameron K. Beattie
In the early days of personal computing, programmers diligently wrote procedural code to create character-based PC applications, knowing that 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 room-size computers of the early days. It's now commonplace for Internet-enabled computers to browse data from hundreds of thousands of computers interconnected around the globe. Now that computers can readily connect to each other, programmers need the tools and a unified architecture that allows them to write modular programs to communicate in a standard, organized fashion. Object technology provides the power to deliver this functionality, and Visual Studio 97 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 with Visual Studio.
Object technologies need an underlying framework that can allow complex and interrelated programs to work together in harmony. The object architecture that Microsoft created--called the Component Object Model or simply 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. n
No matter which Visual Studio programming language you select for your projects, a basic understanding of COM, OLE, and ActiveX is vital for your success. You can still use Visual Studio programming languages 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 shared by all the Visual Studio development languages, and subsequent chapters in this book provide detailed implementation strategies specific to each language.
Users of custom corporate computer applications continue to demand more functionality from their aging systems, and they're 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 documented only sparsely (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 that your company, like many companies, has a legacy application that might give unpredictable results for dates beyond the year 1999. Suppose that rather than be 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 this program's capability to handle dates beyond the year 2000, you have to test only that date-calculation module to see what happens. If it needs repair, you can change the implementation inside 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 can't be updated easily. This modular approach is available now, and you can implement it today by using the programming tools in Visual Studio.
These magic little modules are known abstractly as objects. You can implement computer- modeled objects in software as discrete components that you can create, modify, or reuse as building blocks for complex, enterprisewide applications. Rebuilding existing functionality from scratch is admittedly quite costly and time-consuming. After the foundation is in place, however, 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. Suppose that 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 whether the modem will work, because modems have become a commodity with standard interfaces and communication protocols.
Software developers now seek 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 way, corporations can use existing programming talent in any language supported by the object model implemented on their computer platforms. This language neutrality is accomplished by using programming tools that create components compatible at the binary level. This process allows 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 with similar needs.
If you aren't already familiar with object-oriented programming, you 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 can 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 mem-ory pointers, which then allows code modules from different COM-compliant compilers to operate together. Theoretically, you can build a COM-compatible compiler for any programming language that can create memory structures by using pointers and can call functions through pointers. Within Visual Studio, Visual C++ and Visual Basic both include COM- compatible compilers.
The result for Visual Studio users 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. You can choose from various object-oriented methodologies, 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, the following sections give you a brief review of common terms and notation used in object- oriented programming.
Objects You can think of an OOP object 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. You can set and lock these properties when designing the program, or you can make them available to users to change during runtime. Methods are the named functions or actions that 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 that they can be used without modification in any environment where they're 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 represents only wooden yellow pencils with No. 2 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 means that a new member of the class is created in memory.
See "Objects," Chapter 27
When you define a class in code, you must define all 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 want 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 with object classes for years, but the class structure is a relatively new addition to Visual Basic. 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 enterprisewide applications.
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 later in the section "OOP Advanced Topics."
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 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's usually 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 you're trying to design objects, it's handy to have a common method for representing these programming objects graphically. You can use the syntax object.method to denote invoking a particular method of the server object. There are many variations, but Figure 4.1 shows the typical way to represent an object graphically.
FIG. 4.1
A COM object contains all the methods, properties, and data required by that object.
In Figure 4.1, the object and its contents are shown as rectangles. The interface nodes are 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. They 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. Figure 4.2 shows the relationship between a client and an in-process server.
FIG. 4.2
This client object has obtained the services of a server object on the same computer
and in the same process space. In this relation-ship, this server is a local, in-process
server.
It's 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's 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. Figure 4.3 shows this relationship.
FIG. 4.3
This client object has obtained the services of a server object on the same computer,
but outside its process space. In this relation-ship, 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 is highlighted later in the discussion on distributed computing. Figure 4.4 shows the remote server relationship.
You can conveniently group in various ways objects that perform very specific tasks. These object-grouping techniques are effective ways to reuse objects and minimize maintenance.
Perhaps the most straightforward way is to create a new object to act as a container in which you place the reused objects. This method is referred to as object containment, because the outer object completely contains the inner objects. The interfaces of the inner objects are visible only to the outer object and can't be accessed directly by external objects. Figure 4.5 depicts this relationship.
FIG. 4.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.
FIG. 4.5
This outer object completely contains the interfaces and services of the inner objects,
a collection of reused and new objects that clients can access via commu-nication
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. Referred to as object delegation, this relationship is illustrated in Figure 4.6.
FIG. 4.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 expose their interfaces directly to client objects. This is perhaps the most complicated case to implement because the clients of the inner objects don't directly see the relationships between the inner and outer objects. This relationship, referred to as object aggregation, is shown in Figure 4.7.
FIG. 4.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 on the data and store the resulting output separately from the algorithms. Object-oriented programming is an approach where you can group a code module's related data and implementation code 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, with Visual Studio 97, you can use Visual C++, Visual Basic, and Visual J++ separately or in combination to create robust, enterprisewide 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. Because some languages accomplish OOP objectives more completely than others, many people distinguish between object-oriented languages and object-based languages. Object-based languages implement as many of the modular features of object-oriented programming as possible within design constraints that enable simplicity or backward compatibility for that language. For example, although object-based languages such as earlier versions of Visual Basic allowed you to create object-like structures in code, creating another similar object required that you create it at design time, perhaps cutting and pasting code from the first object.
To qualify as a true OOP object, the programming structure must support the characteristics of encapsulation, polymorphism, and inheritance. Entire books have been devoted to this topic, 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. Suppose that you're 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 that it's time to invoke WashDishes, it can make that request via the interface you've appropriately named IWashDishes. The desired result might be that any instantiated Dishes object with a Clean property value now 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's sometimes practical or necessary to expose the data for direct manipulation by client objects, which 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, you can see polymorphism as the capability for client objects to access the services of different server objects in the same syntactic manner, even when dealing with different object types.
An illustration will help clarify this concept. Returning to the Domestic Simulator example, 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 that 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 object 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 whether 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 object type. 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.
In 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 that the Animal class includes characteristics such as breathing and eating. From the Animal class, you can create subclasses for Wild_Animal and Pet_Animal. These two objects will automatically know 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 Animal objects in general.
COM supports interface inheritance, the capability for one interface definition to inherit characteristics from another interface. COM, however, doesn't support implementation inheritance, 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 doesn't 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. With Visual C++, Visual Basic, and Visual J++, Visual Studio provides a complete set of object-oriented tools that can accommodate the most simple to the most complex object programming projects.
In recent years, incredibly rapid progress has been 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 incremental steps that brought us to our current state of the art should help clear away some of that lingering confusion.
In the 1980s, if you wanted to do some serious object-oriented programming, you could use SmallTalk or C, but programming in those languages was extremely tedious. With the release of the Windows operating system, Microsoft introduced a graphical user interface for the personal computer that used a visual object metaphor but didn't fully qualify as object-oriented. The early versions were slow and unstable, but they succeeded in greatly simplifying program-to-program communication via the Windows Clipboard. Users could run more than one program at a time, each in a separate window. You could even move files and directories simply and easily by dragging and dropping them to new locations.
Although we now take it for granted, being able to copy and paste by using the Clipboard was (and still is) a remarkably handy way to copy data from one document to another without retyping. Using the Clipboard was also a convenient way to move images from one document to another without requiring a separate process to translate the graphics format.
Compound documents were easily created in this manner; unfortunately, it was often difficult to modify the result after the transfer of an image or document. Because usability of PC applications would be improved if the pasted parts could be edited by using the interfaces from their native application environments, Microsoft created the Dynamic Data Exchange (DDE) protocol, which exchanged data by sending commands between applications. This was an improvement but was very slow, very difficult to implement, and less robust than if this capability could be provided as a direct service of the operating system.
In 1991, Microsoft improved the object-enabling concept by introducing a technology called Object Linking and Embedding (OLE 1.0). OLE was a slight improvement to DDE but was still slow and prone to problems due to an underlying architecture that was still a messaging system between applications. On the positive side, OLE-enabled software gave users a more convenient way to store and maintain portions of documents (or entire documents) in a container document. The documents could be from different programs, as long as all the programs were OLE 1.0-enabled. It was now possible, by double-clicking the embedded portion, to open the document and edit it in its native program's controls without leaving the program used for the container document. For example, you could use OLE 1.0 to place an Excel spreadsheet within the text of a Word document and edit it within Word. OLE 1.0 also allowed you to link files so that updates to the original file would be propagated to the linked copies in other documents. OLE 1.0 became well known, well used, and fairly well understood.
Although this embedding capability was extremely useful, OLE 1.0 still didn't qualify as a truly object-oriented technology, because the data was only referenced from a source file and not encapsulated. If you happened to rename the source data file or move it to a new location on disk, you would also unknowingly render all related OLE links unusable. Perhaps the biggest accomplishment of OLE 1.0 was that it popularized the idea that documents should act as containers for functional components of other programs.
This modular component strategy also proved quite useful in the development of applications. Microsoft introduced their first version of Visual Basic not so much to pursue the goals of object technology as to simplify application development for the Windows operating system. Visual Basic succeeded in introducing a vast number of programmers to the graphical user interface paradigm, but early Visual Basic programs tended to be more experimental than useful. Early versions were acceptable for creating small, specialized programs for single users or small user groups on local area networks, but didn't provide the performance or scalability required for building large, mission-critical applications.
Visual Basic did have one critical capability that most other programming languages lacked. Programmers could buy an ever-increasing variety of specialty plug-in components off the shelf from third-party developers. These components, also known as widgets, saved tremendous amounts of programmer time and development dollars because programmers could buy (rather than build) many necessary functional pieces for corporate programming projects. It could even be argued that it was the diversity and quality of the secondary add-on component market--not the quality of the base product--that steadily increased the user base and pushed the continuous improvement of Visual Basic.
Because add-on components extended the capability of Visual Basic, they were assigned the .vbx (Visual Basic extension) filename extension and were commonly referred to as VBXs. Although not true OOP objects, VBX components worked extremely well in providing off-the-shelf, modular functionality. Although the performance of a VBX component was often disappointingly slow, these modular widgets represented solid progress on the road to better object technologies. As a testament to the versatility and popularity of these widgets, you can still find in use today various niche applications relying heavily on specialized VBX components.
NOTE: You shouldn't try to use VBX controls with Visual Studio. VBX controls are 16-bit and aren't compatible with the COM-enabled, 32-bit architecture of Visual Studio languages. However, you can still use VBX controls for applications development for Windows 3.1 by using Visual Basic 3.0 (which is 16-bit) and the 16-bit version of Visual Basic 4.0.
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 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 the OLE functionality by using the new COM architecture. Rather than use a messaging protocol built on top of the operating system (such as DDE or OLE 1.0), 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 2.0. OLE 2.0 eventually provided all the existing features of OLE 1.0, plus a few more.
With the COM communication architecture, OLE 2.0 could now 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 with all capital letters but is commonly pronounced Oh-LAY instead of Oh-el-EE.
NOTE: With the framework in place to create reusable designs independent of the implementations, COM and OLE 2.0 architecture can accommodate any new features and technologies without changing the architecture itself. Many subsequent developments have been built under the OLE banner, but due to the extensible nature of the OLE 2.0 architecture, theoretically no technical reason exists 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.
While the OLE developments were occurring, competition in the visual programming tool arena was heating up. Borland released the Delphi programming environment, which provided some serious competition for Visual Basic in the category of simple but powerful graphical user interface development tools. Despite this game of technical leapfrog with the competition, Microsoft continued to gain market share through an improving product line, brand loyalty from its growing dominance in the desktop suite market, and a highly effective marketing team. 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 the .ocx file extension, and these components became known as OCXs.
OCX components provided modular, 32-bit functionality to all the popular visual program- ming languages, including Microsoft Visual C++ and Microsoft Visual Basic. Although OCX components are used in the Visual Basic toolbox just like VBX components had been, OCXs are actually full-fledged COM objects. The process of converting developers from VBX components to OCX components started gaining significant momentum about the same time that Microsoft decided to expand the use of the OLE terminology.
OLE Controls made up 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 Internet was a much bigger phenomenon than it had anticipated. Microsoft decided to realign its naming conventions 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 the company was producing.
To boldly signal its entry into the booming I-net revolution, Microsoft created a new high-tech Active brand name for present and future I-net related technologies. The company decided to market the remaining OLE technologies as ActiveX, which is easier to pronounce than OLE 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 its I-net intentions by declaring that its new I-net architecture for the PC would be named the Active Platform. At present, the following technologies are included under the Active Platform umbrella:
All the ActiveX technologies are all built to use COM. Not all COM-based technologies fit under the ActiveX umbrella, however--for example, MS Office software and Windows operating systems are COM-enabled but aren't considered part of ActiveX.
In late September 1997, Microsoft introduced the Windows Distributed Internet Applications Architecture (Windows DNA). Windows DNA describes the collection of Microsoft technologies that allow you to integrate client/server and I-net programming. The foundation of Windows DNA is still COM, but COM itself will soon have some new enhancements and be called COM+. This new, improved COM isn't available yet, but the anticipated features are discussed later in the section "Objects over the Horizon: COM+."
Today, probably more confusion exists 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're curious what the names were in late 1997 when this chapter was written, Figure 4.8 shows the relationship of some of these OLE/ActiveX technologies to the COM foundation.
FIG. 4.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 1996, when Microsoft used the Active brand to rename these components as ActiveX controls, allowed 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 the company's aggressive push to develop or enhance its other I-net-related technologies. These products and technologies included Active Server Pages, VBScript, Visual J++, and Visual InterDev, which are all included or supported in Visual Studio. COM will continue to provide the underlying architecture designed to integrate all this new technology seamlessly. This is the brave new computing world your Visual Studio 97 programs will be operating in.
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 (ORB), which you can think of as a very intelligent switchboard providing directory and connection services between client and server objects. Various ORBs and ORB-like technologies are now available, and considerable debate has raged 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 various 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, developed in the early 1990s, has now gained the support of more than 20 major technology vendors, including Apple, Sun, and IBM. OMG has achieved a very impressive membership of more than 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 won't 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 doesn't fit into Microsoft's plans.
ON THE WEB:For more history about CORBA and the Object Management Group, visit OMG's web site at http://www.omg.org.
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 (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.
NOTE: For general discussions about the technology, the term COM is commonly used to denote the COM and DCOM technologies.
COM and DCOM deliver much functionality, but Microsoft is working to make COM even better. In October 1997, Microsoft announced a unified and improved version of COM and DCOM called COM+, which is due to be released in late 1998. COM+ promises to provide faster performance and improved capabilities, but the main thrust will be to relieve you from having to program the object housekeeping details, which Microsoft colorfully refers to as the grungy stuff. Microsoft describes COM+ improvements as an "auto-everything" approach to using objects. You can still program the grungy stuff if you want, but with COM+ the system will do it for you if you choose not to. Because COM+ isn't ready for prime time yet, however, you still need to know and use the basic techniques needed to implement COM objects by using Visual Studio. According to Microsoft, all properly constructed COM objects built now will work equally well or better under the enhancements of COM+.
What would Microsoft innovation be without new names? In the COM+ world, Microsoft's COM ORB will be known as COM Runtime, and the name DCOM will simply refer to the DCE Remote Procedure Call protocol that connects distributed COM objects. COM+ will also provide a new Services layer that provides transaction handling, data binding, load balancing, security, and an event infrastructure. COM+ will integrate tightly with Microsoft Transaction Server (MTS) to provide a full-featured and robust computing environment to host any Windows-based, object-oriented system you can imagine and develop.
See Chapter 24, "Deploying Distributed Applications"See Chapter 25, "Using Microsoft Transaction Server to Enable Distributed Applications"
With the announcement of COM+, Microsoft has made its intention clear that now and in the future, the road to Windows-based, object-oriented software is paved with COM.
ON THE WEB:To keep up with the latest news on COM+, visit Microsoft's COM home page at http://www.microsoft.com/COM/. For more information on Microsoft Transaction Server, visit Microsoft's MTS home page at http://www.microsoft.com/transaction/. At press time, the Windows DNA announcement and answers to frequently asked questions about DNA was posted at http://www.microsoft.com/sitebuilder/DNA/.
COM is Microsoft's way to connect objects, but it isn't the only way. The industry is now grappling with the choice of designing programs to communicate by using a designated open standard (such as CORBA) or to communicate by using COM, the emerging object communication technology that's the de facto standard for Windows-based computing.
CORBA-based technologies are the most significant competing object models you'll encounter when supporting enterprisewide integration of your programs created with Visual Studio. For example, IBM provides a CORBA-compliant object architecture for mainframe systems in its System Object Model (SOM) and also in its Distributed System Object Model (DSOM).
ON THE WEB:If your company has IBM-supported mainframe or workstation equipment, you probably want to become more familiar with SOM/DSOM. For more information, visit IBM's home page at http://www.ibm.com 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 are 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 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, another organization dedicated to supporting standards in the software industry.
ON THE WEB:For more information about The Open Group, visit its web site at http://www.opengroup.org. For an interesting observation about "Who Owns ActiveX," see David Chappell's cover story from the September 1997 edition of Byte magazine. This article also can be found on the web at http://www.byte.com/art/9709/sec5/art7.htm.
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 Studio 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.
ON THE WEB:For more information on COM and ActiveX support for UNIX-based operating systems, visit Software AG at http://www.sagus.com.
There's still a great debate over which object model you should use: COM or CORBA. It would be nice if these two models were compatible, but because that's unlikely to happen in the foreseeable future, you have to make a choice. Despite all the hype, your choice should be based on what you consider better for your own purposes.
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. In that case, it's wise to standardize with products from a single CORBA-compliant vendor because integration between CORBA-based products has sometimes proven to be very difficult. Other ORBs may find niche markets where they can survive, but for enterprises where most users have PCs on their desks, the initial standoff between COM and CORBA will likely give way to integration and then finally to assimilation by COM.
ON THE WEB:The Object Management Group maintains a concise set of links to CORBA-related web sites at http://www.omg.org/new/corbkmk.htm.
Perhaps you're wondering if programs created in Visual Studio 97 (which uses COM) are compatible with CORBA. Such integration isn't yet available out of the box, but third-party tools are now available 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 allows COM objects on Windows platforms to communicate with CORBA objects on UNIX and mainframe operating systems.
ON THE WEB:For more information on VisiBroker, VisiBridge, and other Visigenic products, visit the company's web site at http://www.visigenic.com.
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.
ON THE WEB:More information on Orbix and other Iona products is available at http://www.iona.com.
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 either can communicate directly or are merged into a unified standard. Bridging technology provides valuable integration capability; unfortunately, however, 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 Studio programmers will have much less need for a COM-to-CORBA interface layer.
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 "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 1997, when CILabs essentially surrendered by announcing it was discontinuing support of the OpenDoc architecture.
ON THE WEB:CILabs has since been dissolved by its board of directors. At the date of this writing, the company's farewell greetings were posted at http://www.cilabs.com.
The battle between CORBA and COM to become the generally accepted ORB standard is nowhere near over. Netscape 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 its 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 Navigator/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.
ON THE WEB:Microsoft maintains web pages dedicated to OLE and COM topics at http://www.microsoft.com/oledev/ and http://www.microsoft.com/com/.
Still another battleground for Microsoft's object technologies is in the I-net 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 by 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 created the Visual J++ product 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 or operating environment built on top of the native operating system. Because Java and Java-related technologies are attempting to be a cross-platform solution, the Java language and the JVM must limit themselves 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've developed the most "pure" Java applets or even full-fledged Java applications, you should still test them extensively to make sure that they work correctly on all desired platforms.
With Visual Studio, Visual J++ gives you the option of writing "pure" Java applets, or you can use the new J/Direct interface to allow your applets to access the entire range of Windows-specific APIs. Java purists have panned this flexibility for developing Java applets for the PC. Nonetheless, as a programmer supporting Windows-based users, you're faced with a choice: use "pure" Java to create slower and less functional applets that may or may not work on other platforms, or optimize your code to work in Windows.
JavaBeans, another Java-related technology that's not very popular with Microsoft, 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 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 hopes 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 for the desktop and the I-net, and will need to be accounted for when you're integrating and supporting enterprisewide systems.
ON THE WEB:Microsoft's web site provides various information about its position on these technologies. An easy way to find the most recent update is to do a keyword search at Microsoft's search page at http://www.microsoft.com/search/. A good starting point to locate more information on CORBA is the Object Management Group's web site at http://www.omg.org.
Although COM was originally a Windows-only 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 its internal efforts, Microsoft has negotiated outside contracts to port COM to some of these other platforms. As mentioned previously, COM is native to Windows, and Software AG has already ported COM and ActiveX to the Sun Solaris family of operating systems. By the end of 1998, you also can expect to see COM support released for HP/UX, IBM MVS 5.2.2 (OS 390), IBM OS/400, Digital UNIX 4.0 (Alpha), Digital Open VMS, Siemens Nixdorf SINIX, Linux 2.0, and SCO UnixWare. Hence, if COM isn't yet supported on your computer system of preference, you probably won't have to wait long until it is.
ON THE WEB:To get the most recent information and beta versions of COM ports for these other operating systems, visit Software AG's web site at http://www.sagus.com.
This section takes a closer look at the common implementation requirements of Microsoft's object technologies, while still maintaining language neutrality. It provides a quick introduction to COM-related terms and concepts, which are then 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, you can think of an interface 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 aloud as I-Speak or Interface Speak.
See "Interfaces," Chapter 6
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's 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. Figure 4.9 shows this structure.
FIG. 4.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.
IUnknown One of the most basic rules of the COM specification is that all COM objects must support a specific interface named IUnknown. This 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 by using this predefined interface designed to reveal what interfaces are "unknown." A more technical way of stating the relationship is that an object's interfaces must 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 operation is so routine, Microsoft programming tools accomplish most of this work in the background or allow you to customize some basic example code. Figure 4.9 also shows how a client must request a pointer from IUnknown to access an object's interfaces.
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 to generate and assign a unique number called the globally unique identifier (GUID). A GUID (pronounced as goo-i-d or gwid) 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. You can create a new GUID whenever you need to by using the GUID-generation tools (GUIDGen and UUIDGen) provided in Visual Studio. To create your 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 create a GUID, you can use 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 is 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'll create version incompatibility problems for your users. You'll see an example of this later in the section "Binary Compatibility and 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 users. 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. 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 GUID (as described in the next section). Each version must have a different identifier, and QueryInterface returns a pointer to the version the client specifically asks for.
COM allows 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, the old and new clients always work appropriately. When you create a new version of an interface, it's a good practice to change the name or add a version number after the name to avoid any confusion. With proper versioning, you can safely support old and new objects on either side of the client/server relationship. Figure 4.10 shows this concept, using objects from the Domestic Simulator example.
FIG. 4.10
Mower objects from different vendors will still get the grass cut as long as COM
versioning rules are followed.
Suppose that you 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 its mower object, called Mower97, that's the envy of the simulated neighborhood. Some people buy it to upgrade their simulation, but because 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 allow spouses to be able to use this new variety of mowers, SimWare upgrades its 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 because the versioning was done correctly and each object supported prior interfaces, any combination of spouse and mower objects could successfully mow the lawn. If not, when you drop in a replacement object, you risk breaking the existing object relationships and your application will no longer function 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.
Clients 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, which 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 on request. One way to accomplish this is 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's 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.
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 every single 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's 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're 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 multithreaded. A process capable of multithreading 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 multithreading 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 previously. Similarly, COM uses the term free threads to describe what were previously called worker threads. Regardless of the terminology, COM makes multithreaded 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. The following sections discuss 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 responsi-bility to notify the server object by calling the Release method to decrement 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 need 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. Considerably more complexity is 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 usually connected via a network. 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. With this arrangement (see Figure 4.11), the COM client doesn't need to know where the server object is located because COM creates a proxy or stub as needed, making all servers appear to be in the same process space.
FIG. 4.11
COM proxies and stubs provide each remote object 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 uniformly and transparently.
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 have only a pointer to the proxy, and COM must support interprocess 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 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. This 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 allow 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. Used by OLE Automation servers, the IDispatch interface 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 through persistent storage of object data, uniform data transfer, and connectable objects.
A COM object that can store its data by using a fairly permanent medium (such as in a database on a disk drive) is said to have persistent data. Persistent storage is accomplished natively in COM by means of a structure formerly called OLE structured storage, 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.
Compared with 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. Figure 4.12 compares the familiar DOS/Windows file structure and the COM Compound Storage 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, IDataObject, is defined to accomplish this functionality. 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.
FIG. 4.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.
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 interobject 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. The implementation details for implementing these COM interfaces in Visual Basic and Visual C++ are explored in greater detail in subsequent chapters.
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, because 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 usually have to interoperate during the transition, because instant transitions involving multiple systems are rarely possible. This task is indeed daunting, but the sooner your organization determines its strategy for making the transition, the better prepared you can be for implementing it.
When your company is 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 by 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 you're 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:
Many mission-critical legacy systems are stable, reliably do exactly what they're supposed to, and require very little maintenance. Perhaps this claim sounds amazing, but these applications 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's a compelling reason to change the status quo: the business process changes dramatically, the cost for support becomes unacceptable, or 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'll 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's practical?
Situations involving this approach would include the following:
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 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 users on character-based terminals and provide that data through a new graphical user interface. Visual C++ is probably the most common choice for creating these wrappers, but you can also use Visual Basic or Visual J++.
Although wrappers can be a fantastic method for leveraging your legacy programming in-vestments, 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 will likely 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, which probably has a huge list of higher priority new initiatives and maintenance projects that need your immediate attention. With this in mind, it's 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 multitiered, object-oriented architecture that's 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:
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.
This process can become quite complicated, but fortunately an object modeling tool called Visual Modeler can be downloaded from Microsoft's web site and used with Visual Basic 5. Visual Modeler provides the basic features needed to construct and document your object classes and relationships, and it can save you a tremendous amount of time.
See Chapter 27, "System Modeling and Microsoft Visual Modeler"
In most organizations, the legacy system can't 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 supporting the lowest level functions of the system. Resist the urge to start with the user interface, because you'll quickly become frustrated trying to design interfaces for functions 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. Performance of the main system can be enhanced greatly 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 used by any application on the system modified to take advantage of it.
After these two groups of components are built, tested, and operational, you've 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:
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 doesn't 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.
Visual Studio is particularly well suited to support development of object-oriented browser-based systems. Because DCOM provides your object communication framework, you need to have a DCOM-enabled container for your objects at the user's machine. Your best choice for this container today would be Internet Explorer; when Windows 98 is released, it would be the new Windows 98 Active Desktop. If you're planning to write all your servers by using Visual J++, your user platform options would then include the so-called Network Computer (NC) or WebPC machines that are sparsely equipped network client computers running a standard Java Virtual Machine and little else. Because all your server objects will be accessed across the I-net, that's all that needs to be done at the user's machine.
Visual Studio gives you an even greater degree of flexibility on the server side. Your ultimate goal is to encapsulate all desired services associated with the legacy system into a new set of COM server objects. You can use any of the encapsulation strategies discussed previously, and you can implement it by using COM objects you've created by using any combination of Visual C++, Visual Basic, or Visual J++. Then you can use Visual InterDev to include those objects in a set of active server pages that serves as new the user interface. After your objects and interface pages are developed and tested, you can release them by posting them to a production server platform. Unlike conventional installs (where you interrupt users to make an install), with this approach you must notify your users by other means before you can expect them to start using the new system. But like conventional systems, it's a good idea to train the users before unleashing them on mission-critical data systems.
Suppose that your firm's existing inventory database resides on a legacy mainframe computer and is accessed from PCs by using a terminal emulator. You can implement replacement with new browser-based graphical user interfaces 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 are 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 service. This can be somewhat mitigated by operating mirrored servers at separate locations, so if one server can't 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 have yet to gain universal trust. However, many corporate information systems can be operated safely with the minor degree of risk now associated with I-net communication methods.
See the chapters in Part III, "Developing Internet, Intranet, and Extranet Applications," Chapter 9, for more information about browser-based interfaces for legacy data.
The first order of business for creating new applications is to gather and document as many project requirements as you can. This step can't 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've 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 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, because you should be able to reveal and solve any existing problems with the business processes rooted only in limits imposed by a previous automation system. After you document 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 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 will become the object classes, the adjectives will become the properties, and the verbs will become the methods. Then you can start the design process by establishing the classes and associating each method with the class 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.
This process is simplified significantly by using modeling software. As mentioned previously, you can use Microsoft's Visual Modeler for no charge, or you may want to purchase the more full-featured version called Rational Rose. Regardless of your choice of modeling tool, learn your modeling software well before attempting to use it to build an object-oriented system from scratch. The development time and money saved by effective use of a modeling tool will make it well worth your effort.
See Chapter 27, "System Modeling and Microsoft Visual Modeler"
ON THE WEB:For more information about Rational's object modeling tools, visit its web site at http://www.rational.com.
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 users' needs. Rapid prototyping is also a valuable tool for testing ideas, to ensure that programmers and 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 "difficult" users might not be comfortable, but it will likely yield two very important benefits:
After you and your users agree on a preliminary object design, resist the urge to immediately begin coding. As a rule, it's cheaper and faster to buy an object than it is to write it yourself, so now start looking for prebuilt objects. First look 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 you have thousands of commercially distributed ActiveX components to choose from. Only after you truly exhaust your options for reuse should you pass out the coding assignments.
Now that you're ready to begin writing the code, Visual Studio provides you and your team with enormous flexibility. You may need to use any or all of the approaches mentioned previously, depending on the requirements of your project. Language-specific implementation guidance is provided in subsequent chapters for each Visual Studio tool.
After your objects are developed, tested, and put into production, you need to determine who will 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 abandoning these valuable objects inadvertently, 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, and should be 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.
Now that you have a general understanding of object-oriented programming and Microsoft's object technologies, it's time to get some language-specific instructions on how to create a few of those versatile COM objects known as ActiveX components:
© Copyright, Macmillan Computer Publishing. All rights reserved.