This action might not be possible to undo. Are you sure you want to continue?
By DBMS, June 1995 Ken North
Microsoft's language-independent, binary standard for object sharing on desk-tops and across networks.
The maturation of the computing industry has some parallels with agri-business, where, in developed nations, a few producers supply the needs of many consumers. One widely accepted view of our computing future is that we will eventually become a community with a few producers (object implementors) supplying many consumers (object users). The consumers will be programmers and power users who will build applications by drawing from a stock of interoperable objects before writing custom code or building custom objects to fill in functionality gaps. The Microsoft contribution to this view of computing with shared objects, or component objects, is Object Linking and Embedding (OLE). [For a complete list of the acronyms used in this article, please see Table 1.] OLE provides a language-independent, binary standard for object sharing. Before jumping into this technology, you should understand OLE, the Component Object Model (COM), services that build on COM, and OLE custom controls (OCXs). COM is a core technology for creating shareable binary components. It provides the infrastructure upon which OLE layers other software services. COM is a language-independent binary standard that permits object sharing by applications written in a variety of languages. OCXs are Windows controls that provide data-binding capabilities that make them particularly suited to database work. In addition to these technologies, Microsoft has launched an initiative that creates a data integration layer over COM. (See the sidebar, "Nile, Microsoft's Data Integration Initiative".) In this article, I look at each of these topics in more detail. Microsoft is working on an update to the current OLE 2.0 that will provide interoperable and distributed objects. The company released a revised specification for OLE's COM in March 1995. This specification update discusses distributed objects based on Open Software Foundation's Distributed Computing Environment (DCE) Remote Procedure Calls (RPCs). The COM network protocol is based on the 1994 X/Open DCE RPC Common Applications Environment (CAE) specification. Distributed OLE adds new interfaces related to remote objects, but it is basically an upward extension that shouldn't break existing OLE code. Microsoft considers OLE a precursor to Cairo, its next-generation operating system that will expose operating system services as objects. Other interoperable object solutions that have become prominent recently include the Object Management Group's Common Object Request Broker Architecture (CORBA), Component Integration Lab's OpenDoc, IBM's System Object Model (SOM), and SunSoft's Distributed Objects Everywhere (DOE). OLE 2.0 has a significant following within the developer community, even
though proponents of other technologies such as OpenDoc and CORBA argue that these solutions are technically superior to OLE. CORBA enjoys support in Unix environments, while OLE has become a de facto object standard for Windows computing.
The Rationale for Object Sharing
Some automobile drivers prefer the direct control of a four- or five-speed transmission while others prefer the ease of an automatic transmission. Some programmers work with call-level interfaces (CLIs) such as OSF/DCE or the Windows API; other developers look for object solutions to simplify programming. This latter demographic is the focus for software giants such as IBM and Microsoft, who recognize that capturing the support of this developer group is critical to capturing market share in the 21st century. Objectsharing technology is integral to the architecture of operating systems currently in development. A developer's choice of programming techniques often involves the level of abstraction at which to operate. Object techniques raise the level of abstraction by encapsulating lowlevel programming details. For example, a programmer working at a lower level will dig into DCE's 400-plus system calls to use RPCs. A programmer working at a higher level uses a technology such as OLE, which builds on DCE. OLE encapsulates functionality that lets a developer deal with an object without concern for whether the object is local or remote. OLE objects are defined in a registry, so they are usable across applications and available to scripting languages such as Visual Basic for Applications. Microsoft predicts that programming with OLE interfaces will eventually supplant most detail-level programming in Windows and Cairo applications (much as assembly language gave way to higher-level languages). To understand OLE you should know a few terms and concepts (and a slew of acronyms, as you may have noticed from Table 1). A container is an entity that contains linked or embedded objects. Users of OLE container applications create and manage compound documents that consist of objects from multiple source applications. Objects linked to or embedded within other objects are nested. An application that creates and manages compound documents is a container application. Server applications are object implementors because they create and maintain Windows objects. OLE uses several types of identifiers to define interfaces and object classes uniquely. The universal identifier is the globally unique identifier (GUID), a 128-bit value that provides a unique identity for each interface and object class. OLE also uses a class identifier (CLSID) for every standard object class, and an interface identifier (IID) for every standard interface. Windows Objects are component objects with a unique CLSID. The registration database, or registry, contains the inventory of available objects. It includes a unique CLSID entry for each component object class.
COM and Service Layers
If you understand the rationale for objects, you are probably wondering what services OLE provides to application developers. The short answer is that OLE provides services for object sharing and component integration. The heart of OLE is COM, which is the foundation upon which Microsoft built additional services. Figure 1 shows the various
OLE services, which build on COM. COM provides memory management, error handling, interface negotiation, interprocess communication, and other basic services for component object management. To use OLE, you must install a variety of Windows dynamic link libraries (DLLs), including compobj.dll, the DLL that supports COM. When you instantiate a component object, you receive a pointer to the object's interface. OLE objects implement one or more interfaces that provide access to an object's member functions but not its data. Figure 2 illustrates multiple interfaces for a single object. The instantiation of an interface is an array of pointers to member functions (the implementation of the interface). An interface is analogous to a C++ abstract base class or a collection of methods. Applications use OLE services by accessing the interfaces defined in Table 2. By calling the interface, a program can obtain a pointer to a table that contains an entry for each function available through that interface. The table is a virtual table (vtable or VTBL) whose entries are themselves pointers to functions. OLE supports dynamic binding, so you can determine at runtime what functions an interface provides. To support this type of programming, every object implements an interface called IUnknown, which provides a standard member function called QueryInterface. Instead of resolving addresses and type information at link time, an application can obtain this information from QueryInterface at runtime. All OLE interfaces are derivatives of IUnknown; therefore, every object interface supports QueryInterface. The notation for an OLE call is similar to C++, where you specify a classname::member function. For OLE, you use an interface::member function so that an application that wants to save text to a file would call IPersistFile::Save, where IPersistFile is the interface and Save is the member function (or method). COM uses a client/server model in which object users are clients and object implementors are servers. In-Process servers execute within the client's address space, while Out-of-Process servers are standalone executables. Clients and servers use a class factory, a feature within the server module that creates objects. Client access to the class factory occurs via the server's IClassFactory interface. COM client applications use the same process to instantiate an object whether the server is In-Process, local, or remote. The client passes a CLSID to COM when instantiating an object. To ask the class factory to manufacture one or more objects, the client obtains the pointer to IClassFactory and calls CoGetClassObject. The client can also call COM's CoCreateInstance to create a single instance (one object). Marshalling, the process of passing function calls and parameters across process boundaries, handles differences related to different word sizes. It enables objects having 16-bit parameters to interoperate with those having 32-bit parameters. COMPOBJ.DLL contains the marshalling code that resolves addressing differences, so that object users see an object that matches their own address space and has the correct byte ordering. Marshalling is handled by components called proxy objects. Unmarshalling is handled by stub component objects. Every interface has its own proxy object to package method
parameters for that interface. OLE uses Lightweight Remote Procedure Calls (LRPCs) for marshalling local objects, and DCE-compliant ORPCs ("object" RPCs) for distributed objects. OLE uses an object description language (ODL) and type libraries (TLBs) to describe objects. The text descriptions in ODL are compiled to produce the TLB, which is an OLE compound document file that contains descriptions of types, modules, and interfaces. OLE provides more than basic component object management. The additional services that build upon COM include Structured Storage, Monikers, Uniform Data Transfer, Drag and Drop, Linking, Embedding, In-Place Activation, and Automation. The OCX architecture adds Controls, Property Page, Property Change Notification, Events, and Connectable Objects. OLE implements a standard set of protocols for performing a variety of data-transfer operations, including drag-and-drop, clipboard control, and compound document processing. Uniform Data Transfer simplifies data transfers and change notification (for example, clipboard cut-and-paste operations). In-Place Activation lets users edit, display, record, and play data without switching to a different window. A Moniker is a Windows Object that describes the data source and includes code for data binding. OLE includes five Moniker classes: Composite, File, Item, Anti, and Pointer. Linking places only rendering information and a pointer to an object's data in a compound document. Embedding places the rendering information and the object data within the document. It is possible to edit an embedded object in-place or within its own container, because the compound document includes a copy of the original embedded object and all of the information needed to manage the object.
Automation and Structured and Persistent Storage
OLE Automation lets applications create and expose command sets for interapplication operations. Applications can operate each other without human intervention, and developers can write commands and macros that operate across application boundaries. Automation lets object users manipulate objects by setting properties or calling methods with optional arguments. An interface called IDispatch allows runtime binding to object methods and properties. OLE supports a model for structured storage that uses compound files instead of traditional file-system interfaces, which use handles. OLE's storage.dll provides services for compound files, making it the forerunner of the future Windows file system. OLE Structured Storage includes two object types: streams and storages. Storage objects are similar to directories containing other storage and stream objects. Streams contain unformatted or unstructured data. Storage objects and stream objects support direct data access and a transaction mode for committing changes to data. Objects that are stored using a persistent medium, such as a magnetic disk, are persistent objects. OLE provides several interfaces for persistent objects (such as IPersistFile, IPersistStorage, and IPersistStream). Component objects can save their persistent state by implementing IPersistStream and IPersistStorage.
The third-party market for plug-in components may speed OLE's adoption by Windows programmers. The 16- and 32-bit OCXs are likely to succeed the 16-bit Visual Basic Extension (VBX), the most widely used component software today. OCXs are actually In-Process servers that support in-place activation and OLE automation. OCXs brought new capabilities to OLE, including outgoing interfaces and an event model (which associates events with the code that handles those events) to support event-driven programming. Containers use "outgoing interfaces" to activate their OLE custom controls in response to events.
With distributed OLE, Microsoft intends to support partitioning in order to enable applications to use local or remote components. COM provides location transparency, so an object's programming interface won't change, whether it is local or remote. OLE does the work behind the scenes, and uses ORPCs for remote objects and LRPCs for local objects. To make remote operations transparent, COM disables features that would be appropriate for In-Process objects but are inappropriate for Out-of-Process objects. Microsoft built distributed OLE on a DCE foundation. It uses the DCE security model, naming conventions, and directory services. Also, the OLE Interface Definition Language (IDL) is an extended version of the DCE IDL. Distributed OLE uses DCE RPCs and supports the use of custom RPCs, as long as they are DCE-compliant. It also handles marshalling for objects based on a variety of CPUs. Jeff Alger, one of Microsoft's OLE gurus, describes the next version of OLE as being a "DCE implementation with valueadded for COM. It supplements DCE; it doesn't reinvent it." COM uses proxies and stubs to support object location transparency. Whether an object server is local or remote, it creates a proxy object to act as an In-Process object in the client. The proxy object then talks to stubs that run in the server. A COM component, the Service Control Manager (SCM), manages the connections to remote servers, so that the server is readily accessible when a client issues a request. SCM keeps a database of class information based on registry information. The SCM will return file path information, start an executable, and return a pointer or forward the request to--and maintain--an RPC connection with the remote SCM. OLE also lets you override location transparency with custom marshalling. This capability provides additional control when the separation of interface and implementation introduces too much of a performance penalty across a network.
Error Handling and Debugging
Because OLE's error-handling is language-independent, it is illegal to throw an exception across an interface. (An application signals an error condition by throwing an exception. An error handler uses catch to state that it handles certain types of exceptions.) Instead, COM interface functions use return codes to indicate errors. COM defines an HRESULT type, functions, and macros to handle and report errors. HRESULT is a long integer that consists of 13 bits for facility information and 16 bits for an error code. The facility code
identifies a grouping such as dispatch errors, RPC errors, Win32 errors, persistent-storage errors, and so on. The COM Network Protocol and the COM Library support debugging libraries at either client or server, or on both sides of a COM invocation. The debuggers can use hooks at either end of the RPC infrastructure to invoke debugger actions. Also, COM RPC debugging can use Windows NT and Windows 95 facilities to spawn a debugger when an application faults.
DBMS Interfaces Using OLE
The typical programming interface for database applications today is a CLI in which the functions that provide database services reside in a separate library. To provide OLEbased database access, some companies create an object layer that encapsulates a DBMS CLI. Oracle Corp.'s Oracle Objects for OLE encapsulates the Oracle Call Interface (OCI), and Sylvain Faust Inc.'s SQL Sombrero encapsulates SQL Server's DB-Library and Sybase Open Client's CT-Library. Microsoft Access and Visual Basic feature Data Access Objects (DAO), a collection of objects (such as Databases, Recordsets, and QueryDefs) that work with a variety of databases. The application uses a common programming model to operate against data that may be local or remote, desktop or server, ISAM or SQL. Today, DAO is an application-level object layer, but Microsoft's Nile will extend the scope to make objects available across applications and computers. My article "Multidatabase Development" (DBMS, October, page 76) provides more information about DAO. Database developers are often justifiably concerned about committing resources to technologies that fall out of favor. SQL, ODBC, and the new ANSI/ISO CLI represent standards, but because object interfaces and visual controls are new technologies, some developers worry that they'll have to throw away code. Oracle Objects for OLE is one example of how code can be preserved while migrating to the new technology. Oracle Objects consists of visual controls and an object interface for Visual Basic that replaces the standard VB data control, but does not require major application recoding.
Implications for Client/Server
Today's client/server systems use stored procedures and triggers to implement rules. SQL3 (the next version of the ANSI SQL standard) uses the term "persistent stored modules" to describe procedures. OLE adds the flexibility to use objects at the client, and distributed OLE lets you use objects at the server or on other machines. Distributed objects provide more opportunities for logic distribution than the architectures that we describe now as "two-tier" and "three-tier" client/server systems. For example, repositories and rule-based servers could become factories for rules agents that operate at all levels of a distributed computing environment. OLE has the potential to enable tools such as VB and Powersoft Corp.'s PowerBuilder to use shared objects to implement rules and provide access to repository information. Today, PowerBuilder provides extended attributes and event scripts to validate input, and
VB provides methods for data validation, but client/server developers using those products often rely on server stored procedures to centralize more elaborate rules. OLE lets developers encapsulate rules in objects that are accessible to PowerBuilder, VB, or other OLE-enabled applications. For example, you could define the rules related to an organization's group health plan as OLE objects that are accessible to programmers and applications such as Excel. Repositories and distributed OLE will enable servers and client applications to use the same objects. This will help the development of enterprisewide applications that use common rules.
Industry Support for OLE
At the February Software Development '95 Conference in San Francisco, Microsoft presented a chart that showed OLE on a growth curve that closely parallels Windows. According to Microsoft's research, there are currently 400 to 500 OLE applications from more than 300 independent software vendors. Database companies such as Oracle, Powersoft, and Gupta Corp. have developed new products or have added OCXs to existing products. Microsoft supports OLE for Windows and the Macintosh, while Bristol Technology Inc. offers OLE for Unix platforms. Bristol's Wind/U is a toolset that lets developers use Microsoft Foundation Classes and the Windows API while developing for Unix. Scott Wingo, one of the principal engineers of Wind/U, says Bristol plans to support Microsoft Foundation Classes 3.0 and OLE 2.0 with Wind/U by the summer of 1995. There is other evidence of strong OLE support in the marketplace. A consortium of computer-aided design vendors and geographic information system software vendors recently adopted an OLE extension, OLE for Design and Modeling Applications, that supports embedding of technical graphics in 2D and 3D documents. Borland's C++ supports OLE 2.0 development with an engine (BOCOLE) that encapsulates OLE, and provides a complementary class library called the Object Components Framework. Microsoft's Visual C++ and the Microsoft Foundation Classes are popular for developing OLE-enabled applications. Visual C++ also includes an OLE Control Development Kit for developers who want to create OCXs. Visual Basic 3.0 can operate only as an OLE Automation client; however, Visual Basic 4.0 (scheduled for release when Windows 95 ships) will act as an Automation server and an OCX container.
The Great Object Debate
Comparisons of OLE with other object technologies often evolve into debates about subclassing, implementation inheritance, and compound documents. Subclassing is the process of deriving an object from a parent and modifying its behavior. OLE supports interface inheritance, but not implementation inheritance. One OLE object uses another object, rather than inheriting the object's implementation. Critics assert that OLE should support subclassing or inheritance of an object's implementation. Microsoft rebuts that argument by citing the lack of literature about guaranteed behavior and safe inheritance when implementing a binary object standard. Comparisons of OLE and OpenDoc usually focus on the compound document architecture. OLE proponents raise questions about OpenDoc's ability to operate across
multiple process spaces or the need to distribute parts viewers for compound documents. Despite the debate, developers who support other interoperable object technologies recognize OLE's growing popularity. Novell uses OLE as part of the infrastructure for the Windows version of OpenDoc. OMG members have also been reviewing proposals for CORBA and OLE interoperability. Regardless of the technical merits of each technology, it appears that OLE is here to stay. Ken North is a consultant, author, and software developer. This article includes excerpts from his book Windows Multi-DBMS Programming, (John Wiley & Sons, 1995). You can contact Ken via CompuServe at 71301,1306 or at Resource Group Inc., 2604B El Camino Real, Ste. 351, Carlsbad, CA 92008-1234. * Borland International, 100 Borland Way, Scotts Valley, CA 95066; 800-233-2444 or 408-461-9000. * Bristol Technology Inc., 241C Ethan Allen Highway, Ridgefield, CT 06877; 203-4386969 or fax 203-438-5013. * Gupta Corp., 1060 Marsh Rd., Menlo Park, CA 94025; 800-876-3267, 415-321-9500, or fax 415-321-5471. * Microsoft Corp., One Microsoft Way, Redmond, WA 98052-6399; 800-426-9400 or 206-882-8080. * Novell Inc. 122 East 1700 South, Provo, UT 84606-6194; 800-453-1267, 801-4297000, or fax 801-429-5155. * Oracle Corp., Desktop Products Division, 400 Oracle Parkway, Redwood Shores, CA 94065; 800-633-0583 or 415-506-7000. * Powersoft Corp. (a Sybase company) 561 Virginia Rd., Concord, MA 01742-2732; 800-273-2841, 508-287-1500, or fax 508-369-3997. * Sylvain Faust Inc., 880 Boul. de la Carriére, Suite 120, Hull, Quebec, Canada J8Y 6T5; 800-567-9127 or 819-778-5045.
TABLE 1. Alphabet soup
CAE CLI CLSID COM CORBA DAO DCE DOE GUID IDL IID OCI OCX Common Applications Environment call-level interface class identifier Component Object Model OMG's Common Object Request Broker Architecture Data Access Objects OSF's Distributed Computing Environment SunSoft's Distributed Objects Everywhere globally unique identifier Interface Definition Language interface identifier Oracle Call Interface OLE custom controls
ODL OLE OMG OSF RPC SCM SOM TLB VBA VBX
object description language object linking and embedding Object Management Group Open Software Foundation remote procedure call Service Control Manager IBM's System Object Model OLE type library Visual Basic for Applications Visual Basic extension
NILE: Microsoft's Data Integration Initiative
To improve access to heterogeneous data sources, Microsoft is developing "Nile." That is the working name for a high-level object interface that will integrate data from relational, network model, and hierarchical databases, as well as from flat files, spreadsheets, and other sources. On Microsoft platforms, Nile will act as a layer over COM services and lower-level technologies such as ODBC, which it will use for relational database access. Because it will be a high-level specification, theoretically, other vendors could implement Nile on non-Microsoft platforms using services available in those operating systems. The need for a stable object interface for data access is apparent. Today, components and object interfaces lack the consistency to provide "plug-compatible" operation. For example, Novell's Visual AppBuilder Connection Object (an AppWare Loadable Module) may not behave like Intersolv Inc.'s MultiLink/VB Connect Control (a Visual Basic control) or like Sylvain Faust's SQL-Sombrero Connection Object (an OLE custom control). The purpose of an abstraction or object layer is to provide an interface to a virtual DBMS that maintains a stable interface, even if the underlying data sources change. Microsoft hopes that Nile will provide that stable interface, even when the underlying data does not reside in a DBMS. As with OLE and ODBC, Microsoft plans to subject Nile to industry review before publishing it as an open specification. As a published spec, Nile is likely to move to multiple platforms, as we have seen recently with ODBC and OLE. In February 1995, Microsoft met with several dozen ISVs to review the first two sections of the draft. Reviews of the other sections were scheduled to follow soon thereafter. The Nile specification currently doesn't address the issue of integrating with a repository such as the one that Microsoft is developing with Texas Instruments. Also, Microsoft's initial review of Nile did not cover transaction processing, but later reviews will cover that topic. --Ken North
--OLE is a layered architecture where OLE services such as In-Place Activation and Linking use lower level services such as Structured Storage. OLE builds on an infrastructure of services for Component Objects known as the Component Object Model.
--Windows Objects can expose multiple interfaces such as Interface A and Interface B. An object's interface (its methods or member functions) is accessible through virtual tables that contain a pointer to each member function. The data that the object encapsulates is not exposed to an object user.
Table 2. Applications use OLE 2.0 services by accessing these interfaces
Component Object Interfaces Interface Purpose IClassFactory The interface through which server and container applications create instances of an object class. IEnumX Iterates through an item list. IExternalConnection Implemented by DLL object applications to provide an orderly shutdown of object links. IMalloc Used by OLE to allocate and free memory. IMarshal Provides process space transparency of interface pointers for lightweight remote procedure calls. IStdMarshalInfo Returns the class ID (CLSID) of the object handler that is to marshal data to and from the object. IUnknown The base interface that is common to all OLE applications. Compound Document Interfaces Interface Purpose
Receives asynchronous notifications from embedded or linked objects. IAdviseSink2 Receives notifications of link source changes. IEnumOLEVERB Enumerates the verbs available for an object. IOleAdviseHolder Keeps track of IOleObject::Advise calls and sends notification to registered links. IOleClientSite Provides services to an OLE object from its container. IOleContainer Enumerates objects in a container. IOleItemContainer Used for binding item Monikers. IOleObject Provides a variety of member functions to manage OLE objects such as getting Monikers, CLSIDs, clipboard data, and so on. IRunnableObject Indicates to object handlers and DLL object applications when to run or become a contained object. Data Transfer/Caching Interfaces Interface Purpose IDataAdviseHolder Keeps track of IDataObject::DAdvise calls and sends change notifications to object handlers and servers. IDataObject Supports format enumeration, data retrieval, transfers to and from objects, and notification of object changes. IEnumFORMATETC Enumerates object data formats. IEnumSTATDATA Enumerates an object's advisory connections. IOleCache Controls the data cached inside an embedded object and determines the container's access to data when the object's server is unavailable. IOleCache2 Extends IOleCache to permit clients to update each of the maintained caches. IOleCacheControl Used by object handlers and DLL object applications to associate the cache part of the handler with the running object's IDataObject implementation. IViewObject Provides an object image or picture using a caller-specified device context. IViewObject2 An extension to IViewObject to provide containers and object handlers with the view extents of an object. Linking Interfaces Interface Purpose IBindCtx The bind context is used internally for purposes such as managing the list of bound objects. IEnumMoniker Enumerates the Monikers of which an object is a part. IMoniker Accesses and controls Monikers, and provides object binding.
IOleLink IParseDisplayName IRunningObjectTable Structured Storage Interface IEnumSTATSTG ILockBytes
Provides an interface for updating the Moniker inside a linked object and manipulating its update options. Parses an object Moniker's display name. Provides an interface to the global inventory of currently running objects . Purpose Used by OLE to enumerate IStorage objects. Saves compound document objects to disk-based compound files, byte arrays, and custom storage such as relational databases. Obtains an object's CLSID. Parent of IPersistStorage, IPersistStream, IPersistFile. Used to load documents that reside in a file. Provides methods that containers can call to have a server load and save data. Used to save and reload objects stored in a serial stream. Switches the underlying disk file where objects are saved. Instantiates a directory-like collection of storage and stream objects. Manipulates the underlying bytes of data that comprise an IStorage object. Purpose Provides feedback and status information (for example, key state) to applications implementing drag-and-drop. Implemented to communicate status (for example, key state and mouse location) with the drop source by applications that support dropped data.
IPersist IPersistFile IPersistStorage IPersistStream IRootStorage IStorage IStream Drag and Drop Interface IDropSource IDropTarget
In-Place Activation Interface IOleWindow
Purpose Contains methods that obtain the handle of the in-place window. IOleInPlaceObject Activates and deactivates an in-place object. IOleInPlaceActiveObject Provides communication between the in-place object and the frame and document windows. IOleInPlaceUIWindow Manipulates the container's document window. IOleInPlaceFrame Controls the application's top-level frame window. IOleInPlaceSite Provides an interface to the object's in-place client site.
Concurrency Management Interface Purpose IMessageFilter Filters Windows messages while waiting for responses from synchronous calls. Programmable Controls Interface Purpose IOleControl Used by a control to communicate with its container. IOleControlSite Used on a container's site objects to communicate with a control. IConnectionPoint Used to enumerate connection points for event dispatching. Container IConnectionPoint Specifies a dispatch point for an event. Source: Ken North's Windows Multi-DBMS Programming (John Wiley & Sons Inc., 1995). Subscribe to DBMS and Internet Systems -- It's free for qualified readers in the United States June 1995 Table of Contents | Other Contents | Article Index | Search | Site Index | Home DBMS and Internet Systems (http://www.dbmsmag.com) Copyright © 1995 Miller Freeman, Inc. ALL RIGHTS RESERVED Redistribution without permission is prohibited.
OLE Component Object Model
0. Intended Use OLE (and its Component Object Model) provide a basis for application interoperation in the Microsoft Windows environment. 1. Basic Concepts Microsoft's OLE provides an application integration framework for Microsoft Windows. OLE defines the Component Object Model, which specifies a programming-languageindependent binary standard for object implementations (i.e., it specifies what the implementation of the objects has to look like). Any object conforming to this standard is a legitimate Windows Object, no matter what language is used to implement it. The programming model is synchronous, based on a "Lightweight Remote Procedure Call" (lightweight because, at least at the moment, the calls are not really remote; they are all made on one machine). In the Component Object Model, the concept of interface assumes great importance. An interface is "a set of semantically related functions implemented on an object" [Bro94a]. The Component Object Model uses the word "interface" by itself to refer to the definition (signatures) of those functions. An implementation of an interface is an array of pointers to functions. Any code that has a pointer to that array can call the functions in that interface. A Windows Object implements one or more interfaces, i.e., provides pointers to function tables for each supported interface. Users of objects always obtain and act through pointers to object interfaces; users never obtain pointers to an entire object. For example, when the user of some object first obtains a pointer to the object, the user actually gets a pointer to one of the object's interfaces. This pointer allows the user to call only the functions in that one interface's function table. Through this pointer, the user has no access to any state of the object, nor does the user have any direct access to functions in other interfaces. OLE defines a standard function, called QueryInterface, through which the user of one interface of an object can obtain a pointer to another interface of the same object. QueryInterface is part of an interface called IUnknown, which defines a group of fundamental functions that all Windows Objects support (thus IUnknown is supported by all objects). All other interfaces in OLE are derived from IUnknown, so all interfaces contain the QueryInterface function. This insures that navigation is always possible between the interfaces of a given object. Using QueryInterface, the user of an object can discover the capabilities of that object at run-time by asking for pointers to specific interfaces. This enables an object to implement as many interfaces as it wants. Because all Windows Objects implement at least IUnknown, there is always some basic way for a user to communicate with any object. The function table that implements an interface is designed to have a layout that is identical to the one generated by many C++ compilers. This layout allows a single
indirection (->) on the pointer to call an interface function. However, while this makes the use of C++ to program OLE convenient, this is not a requirement. An object implementation is only required to provide separate function tables for each supported interface. How these tables are created can differ, depending on the particular language used. Because neither use or implementation of a Windows Object is dependent on the programming language used, the Component Object Model is referred to as a binary standard. This provides for language independence without involving the definition of a separate language (e.g., an Interface Definition Language). 2. Objects The Component Object Model specifies a programming-language-independent binary standard for object implementations (i.e., it specifies what the implementation of the objects has to look like). Any object conforming to this standard is a legitimate Windows Object, no matter what language is used to implement it. Users of objects always obtain and act through pointers to object interfaces. An implementation of an interface is an array of pointers to functions (the functions themselves are implemented by the object). Any code that has a pointer to that array can call the functions in that interface. A Windows Object implements one or more interfaces, i.e., provides pointers to function tables for each supported interface. Users never obtain pointers to an entire object. For example, when the user of some object first obtains a pointer to the object, the user actually gets a pointer to one of the object's interfaces. This pointer allows the user to call only the functions in that one interface's function table. Through this pointer, the user has no access to any state of the object, nor does the user have any direct access to functions in other interfaces. A Windows Object is any object, in whatever form, that supports at least one predefined interface, called IUnknown. As part of the IUnknown interface, OLE defines a standard function, called QueryInterface, through which the user of one interface of an object can obtain a pointer to another interface of the same object. QueryInterface takes as input a pointer to an interface identifier (IID) for the desired interface, and either returns an error (and a NULL pointer), meaning the object does not support the interface, or a valid pointer to the new interface. [See entry under 6. Identity, Equality, Copy for a discussion of IIDs.] All other interfaces in OLE are derived from IUnknown, so all interfaces contain the QueryInterface function (the other two functions of IUnknown are AddRef and Release). This insures that navigation is always possible between the interfaces of a given object. A Windows Object must be able to provide a separate function table for each interface it supports. The implementation of the IUnknown functions in each supported interface must be "aware" of the entire object, because they must be able to access all other interfaces in the object and must be able to affect the object's reference count. The implementation in the component object library (COMPOBJ.DLL) provides a small number of fundamental API functions that permit creation of what is called a Component
Object, a special type of Windows Object identified with a unique class identifier that associates an object with a particular DLL or EXE in the file system. A Windows Object does not always need to be structured as a Component Object such that the API functions in COMPOBJ.DLL can create it. Use of such API functions is merely one way through which an initial pointer to an object can be obtained. Unlike C++, where objects are defined using class definitions which generate userdefined types, Windows Objects are defined in terms of the interfaces they support. Since all objects support at least one interface (IUnknown), all Windows Objects are at least of type IUnknown, and can be treated as being of another type by using a different interface. Because of this mechanism, there is no single user-defined type associated with a Windows Object class, as there is with a C++ class. In fact, there is no specific way to identify a specific object. This is because object references (pointers) in Windows Objects are not references to the object itself, as in C++, but rather are pointers to one of the object's interfaces. Given a pointer to an interface, the user can access only functions contained in that interface. The user can never have a pointer to the whole object (because there is no direct user-visible concept of "whole object"), so there is no direct access to state, and no concept of "friend" as in C++. Through the IUnknown interface, a user can obtain pointers to other interfaces that the object also supports, but this means obtaining a different pointer that refers (indirectly) to the same object. Each pointer to an interface points to a function table associated with the object, and each table contains only functions for a specific interface. Because a pointer to a Windows Object always points to a function table, such a pointer can also be used from within programs written in languages other than C++, such as C or assembly code. The list of interfaces that an object of a specific class supports is constant only within a specific object's lifetime, and can vary between different instances of objects of the same class. It cannot be assumed that if Object 1 of class X supports a particular set of interfaces, Object 2 of class X does as well. (Note that the "class" denotes the application providing the object's implementation, not the set of interfaces (type) supported). It also cannot be assumed that if objects of class X once supported interface Y, they always will, because the object might change later. This provides justification for the QueryInterface mechanism of dynamically finding out about interfaces. It is always possible to find out about other interfaces the object supports; thus, if an object is acquired as an instance of a "superclass", it is possible to find out what specific "subclass" it is by examining the other interfaces at runtime. There is no requirement to always treat an object as an instance of the type (interface) through which it was originally acquired. 2.1 operations Operations resemble standard C++ functions, and are defined as part of interface definitions. Operations are always invoked indirectly, through interfaces, as described in the entry under 2. Objects. 2.2 requests
Requests resemble calls to C++ functions. However, functions are always called indirectly, through interfaces, as described in the entry under 2. Objects. 2.3 messages 2.4 specification of behavioral semantics 2.5 methods Methods in the Component Object Model are essentially equivalent to C++ member functions. 2.6 state State in the Component Object Model consists of a set of stored values essentially equivalent to C++ data members. 2.7 object lifetime In C++, objects are constructed using the class's constructor function. There are a number of ways to create a Windows Object, but a common way is to use a class factory object. A class factory object represents a specific class identifier, is obtained by a specific OLE function, and supports an interface named IClassFactory. The IClassFactory interface contains a function named CreateInstance, to which is passed an identifier of the desired interface to that object. The expression IClassFactory::CreateInstance is the logical equivalent of C++'s new. In C++, an object is destroyed by calling the delete operator on an object pointer (which ultimately causes the object's destructor function to be called). The corresponding function that frees a Windows Object (and essentially calls its destructor) is a function called Release. This function is part of the IUnknown interface, and is thus present in every interface. However, calling Release does not necessarily destroy the object. Internally, the object maintains a count of how many references exist to any of its interfaces. Creating an interface pointer increments the reference count, whereas Release decrements it. When the count is reduced to zero, the object frees itself, calling its own destructor. 2.8 behavior/state grouping The Component Object Model implements a classical object model. 2.9 communication model The programming model for Windows Objects is synchronous, based on a "Lightweight Remote Procedure Call" (lightweight because, at least at the moment, the calls are not
really remote; they are all made on one machine). Further development will allow the lightweight RPC to be replaced by genuine (distributed) RPC. 2.10 events OLE handles event notifications through an object called an advise sink--that is, an object that absorbs notifications from a source. The advise sink not only handles notifications for data changes, but it also is generally used to detect changes in another compound document object, such as when it is saved, closed, or renamed. Specifically, an object that is interested in being notified about changes to a specific data object implements an object with an IAdviseSink interface, and passes a pointer to this interface to the data object using the DAdvise function of the IDataObject interface (see entry under 9.2 attributes). Whenever its data changes, the data object calls the OnDataChange function in the IAdviseSink interface it has been passed. (The IAdviseSink interface also contains other notification functions, such as OnRename, OnSave, etc.) 3. Binding "Binding", meaning the choice of a method to be executed in response to a request, is handled by directly calling the object function identified in the request. OLE does not support implementation inheritance (see entry under 8. Inheritance and Delegation), so there is no dispatching by searching a class hierarchy. The term "binding" as used in the OLE context has a somewhat different meaning, relating to the use of a type of object called a moniker in object linking. See entry under 9.6 other. 4. Polymorphism The Component Object Model is polymorphic in the sense that what appears to be the same request can be sent to any interface supporting the requested operation; the interfaces need not refer to objects of the same class. However, unlike models supporting a "conventional" subtyping mechanism for objects having a single interface, the interfaces in Windows Objects remain distinct. Through a pointer to a Y interface, the object cannot be treated as an X, even if the object also has an X interface; instead, the user must explicitly get a pointer to the X interface. See also entry under 8. Inheritance and Delegation. 5. Encapsulation Objects may only be accessed through the operations defined in one of the object's interfaces. Moreover, given a reference to one interface, only the operations in that interface may be used. Operations in another interface may only be used after first obtaining a reference to that interface. 6. Identity, Equality, Copy
Object references (pointers) in Windows Objects are not references to the object itself, as in C++, but rather are pointers to one of the object's interfaces. In fact, there is no specific way to identify a specific object (i.e., it is only possible to obtain references to interfaces, not whole objects). Given a pointer to an interface, the user can access only member functions contained in that interface. The user can never have a pointer to the whole object (because there is no direct user-visible concept of "whole object"), so there is no direct access to data members, and no concept of "friend" as in C++. Through the IUnknown interface, a user can obtain pointers to other interfaces that the object also supports, but this means obtaining a different pointer that refers (indirectly) to the same object. Each pointer to an interface points to a function table in the object, and each table contains only member functions for a specific interface. Because a pointer to a Windows Object always points to a function table, such a pointer can also be used from within programs written in languages other than C++, such as C or assembly code. Every interface is associated with an interface identifier, or IID. An IID is a special case of a universally unique identifier, or UUID. The universally unique identifier is also known as the globally unique identifier, or GUID. GUIDs are 128-bit values created with a DEFINE_GUID macro. Every interface and object class uses a GUID for identification. As described in the OLE SDK, Microsoft will allocate one or more sets of 256 GUIDs for a developer's exclusive use on request. Alternatively, a user with a network card can run a tool UUIDGEN.EXE that will provide a set of 256 GUIDs based on the time of day, the date, and a unique number contained in the network card [Bro94a]. OLE defines IIDs for every standard interface along with class identifiers (CLSID) for every standard object class. When a function is called that asks for an IID or CLSID, what is actually passed is a reference to an instance of the GUID structure that exists in the process space (using the reference types REFIID or REFCLSID). To compare two GUID, IID, or CLSID values for equality, the functions IsEqualGUID, IsEqualIID, and IsEqualCLSID are used. In C++, an overloaded "==" operator can be used. The QueryInterface function must always behave according to specific rules which, among other things, implement an indirect concept of object identity. First, any call to QueryInterface through any interface on a given object asking for a pointer to the IUnknown interface always returns an identical pointer value. This means that, given two arbitrary interface pointers, it is possible to determine whether they belong to the same object by asking each for an IUnknown pointer and comparing the returned pointer values. If they match, both interface pointers refer to the same object. Second, after an object is created, the interfaces it supports are static. If QueryInterface succeeded for a particular interface at one point in the object's lifetime, an identical call to QueryInterface at a later time will also work. (This does not mean that the exact pointer values returned will be identical, just that the interface is always available). The static set of available interfaces applies to a specific object, not an object class. That is, two objects of the same class might not both support the same interfaces, but during the lifetime of each, the interfaces they each support will remain static. Finally, as along as an object is in existence, all interface pointers obtained on that object must remain valid, even if the Release function has been called through those pointers.
7. Types and Classes Unlike C++, where objects are defined using class definitions which generate userdefined types, Windows Objects are defined in terms of the interfaces they support. Since all objects support at least one interface (IUnknown), all Windows Objects are at least of type IUnknown, and can be treated as being of another type by using a different interface. Because of this mechanism, there is no single user-defined type associated with a Windows Object class, as there is with a C++ class (examples of standard Windows Objects include such things as windows, dialogs, messages, controls, and GDI objects, such as pens, brushes, fonts, and bitmaps). A Windows Object class is identified as such only through a class ID (a structure called CLSID) that associates an object with a particular DLL or EXE in the file system (e.g., the application that implements the object). The class ID is stored in a registration database, along with information that defines where the object "lives" and characteristics that a potential user may wish to know without having to actually instantiate the object. The registration database is stored in REG.DAT in the Windows directory. Under Windows Objects, a class object represents a specific class ID, is obtained by a specific OLE API, and supports an interface called IClassFactory. Every component object class (but not all types of Windows Objects) must have a unique CLSID associated with it in the registration database (i.e., Windows Objects can exist that do not have defined classes in this sense). See also entry under 2. Objects. 8. Inheritance and Delegation Windows Objects and the classes they identify through class identifiers have no notion of implementation inheritance. One Windows Object does not inherit the implementation of another Windows Object. Instead, reuse of objects is supported through the containment and aggregation mechanisms. In the Component Object Model, inheritance is simply considered as a language-specific tool (e.g., in C++) that may be useful for implementing classes and defining interfaces in that language. The reason given for not supporting inheritance is that systems built on it must ship all their source code in order to be useful [Bro94a]. For example, inheritance cannot be used to inherit from objects used in the operating system itself, for which source code is not available. In the Component Object Model, both the containment and aggregation mechanisms work by using the implementation of another object. However, the object being used remains entirely self-contained and operates on its own instance of data. The containing object also works on its own data, and calls the other object as necessary to perform specific functions for which it can be passed the data on which to operate. To implement what corresponds to a subclass Y of a class X using containment, class Y completely contains an X object and implements its own version of the X interface which it exports to clients. This makes Y a simple user of X, and X need not know about its use
within Y. This is useful when Y needs to override some aspect of X's behavior. Since all external calls go to the Y implementation first, Y can either override selected behavior or pass the calls directly through to X. To implement what corresponds to a subclass Y of a class X using aggregation, class Y directly exposes X's interface. This requires that X "know" that its interface is exposed for something other than itself, such that the QueryInterface, AddRef, and Release functions behave as a user expects (e.g., X's QueryInterface function must be capable of returning references to interfaces implemented by Y which, as a part of an X interface, it did not originally know about; OLE provides a mechanism for dealing with this when an aggregate is created). Windows Objects do support a specific case of interface inheritance, in that all other interfaces derive from IUnknown, as described in the entry under 2. Objects. Generally, however, unlike models supporting a "conventional" subtyping mechanism for objects having a single interface, the interfaces in Windows Objects remain distinct. Through a pointer to a Y interface, the object cannot be treated as an X, even if the object also has an X interface; instead, the user must explicitly get a pointer to the X interface. C++ multiple inheritance is a convenient way to provide multiple function tables for each interface, since the compiler generates them automatically. Because each implementation of a C++ member function is already part of the object class, each automatically has access to everything in the object. [Bro94a] also discusses a more general approach to constructing objects, for use by programmers in C and other languages that do not provide built-in inheritance. In this approach, a C++ object class corresponding to the Windows Object class inherits from IUnknown, and implements these functions to control the object as a whole. Each interface supported by the object is then implemented in a separate C++ class that singly inherits from the interface it is implementing. These "interface implementations" are instantiated with the object, and live as long as the object lives. The IUnknown members of these interface implementations always delegate to some other IUnknown implementation, which in most cases is the overall object's IUnknown. Each interface implementation also holds a "back pointer" to the object in which the implementations are contained so that they are able to access information centrally stored in the object. In C++, this generally requires that each interface implementation class be a friend of the object class. 9. Noteworthy Objects 9.1 relationships 9.2 attributes As described in the entry under 2. Objects, Windows Objects are accessed through interfaces consisting of sets of functions. Windows Objects that include "data" to be made available to users can be defined with a special interface for accessing that data. Specifically, a data object is a Windows Object that provides a standard data transfer
interface called IDataObject. IDataObject includes, among other things, functions for getting and setting data (GetData, SetData, GetDataHere), for querying the ability of the object to provide data in specific formats (QueryGetData), and for notifying clients of the data when the data changes in various ways(DAdvise, DUnadvise). 9.3 literals 9.4 containment See entry under 8. Inheritance and Delegation. 9.5 aggregates See entry under 8. Inheritance and Delegation. 9.6 other The use of the object linking supported by OLE can introduce problems in maintaining referential integrity. Specifically, since the data referenced by linked objects lives in a separate file on the file system, links are easily broken when the end user manually changes the location of that file...To solve most of the link breakage problems as well as to provide for arbitrarily deep object nestings, OLE uses a type of object called a moniker. A simple moniker contains some reference to a linked object and contains code that knows how to "bind" to that linked object. Binding is the process of launching the application that handles the class of the linked object, asking the application to load a file in which the object lives, then asking the application to resolve the name of the object down to an object pointer. A file moniker is used to store either an absolute or relative pathname. A linked object maintains an absolute moniker and a relative moniker. If it fails to locate the file with the absolute, it tries the relative moniker. Complex object references are described using composite monikers that are sequences of any other simple or composite monikers. Most links can be expressed in a composite of one file moniker and one item moniker, i.e., a link to an embedded object (the item) in a container document (the file). The item name is only meaningful to the application that created it. That application will be asked later to return a pointer to the object identified by the item name...An example of a composite moniker might be one that contains a file (a spreadsheet) and an item moniker (a cell reference). Editor's note: For the benefit of you word origin fans, a "moniker" (or "monica") was originally a nickname taken by a hobo (one of Jack London's was "Skysail Jack"). Later the term became used colloquially to mean any form of name for a person, including an alias, or his or her real name.
10. Extensibility 10.1 Dynamic 10.2 Metaclasses/Metaobject Protocol 10.3 Introspection 11. Object Languages The Component Object Model specifies a programming-language-independent binary standard for object implementations (i.e., it specifies what the implementation of the objects has to look like). Any object conforming to this standard is a legitimate Windows Object, no matter what language is used to implement it. 12. Semantics of Base Classes (+ type constructors) 13. Background and References [Bro94a] K. Brockschmidt, Inside OLE 2, Microsoft Press, Redmond, 1994. [Bro94b] K. Brockschmidt, "OLE 2.0 Part I: Windows Objects and the Component Object Model", Microsoft Systems Journal, Aug. 1993. [Bro94c] K. Brockschmidt, "OLE 2.0 Part II: Implementing a Simple Windows Object Using Either C or C++", Microsoft Systems Journal, Sept. 1993.
This action might not be possible to undo. Are you sure you want to continue?