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.
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 low-level 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.
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.
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.
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 value-added 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.
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.
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.
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 enterprise-wide applications that use common rules.
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.
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.
CAE | Common Applications Environment |
CLI | call-level interface |
CLSID | class identifier |
COM | Component Object Model |
CORBA | OMG's Common Object Request Broker Architecture |
DAO | Data Access Objects |
DCE | OSF's Distributed Computing Environment |
DOE | SunSoft's Distributed Objects Everywhere |
GUID | globally unique identifier |
IDL | Interface Definition Language |
IID | interface identifier |
OCI | Oracle Call Interface |
OCX | OLE custom controls |
ODL | object description language |
OLE | object linking and embedding |
OMG | Object Management Group |
OSF | Open Software Foundation |
RPC | remote procedure call |
SCM | Service Control Manager |
SOM | IBM's System Object Model |
TLB | OLE type library |
VBA | Visual Basic for Applications |
VBX | Visual Basic extension |
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 |
IAdviseSink | 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 | Provides an interface for updating the Moniker inside a linked object and manipulating its update options. |
IParseDisplayName | Parses an object Moniker's display name. |
IRunningObjectTable | Provides an interface to the global inventory of currently running objects . |
Structured Storage | |
Interface | Purpose |
IEnumSTATSTG | Used by OLE to enumerate IStorage objects. |
ILockBytes | Saves compound document objects to disk-based compound files, byte arrays, and custom storage such as relational databases. |
IPersist | Obtains an object's CLSID. Parent of IPersistStorage, IPersistStream, IPersistFile. |
IPersistFile | Used to load documents that reside in a file. |
IPersistStorage | Provides methods that containers can call to have a server load and save data. |
IPersistStream | Used to save and reload objects stored in a serial stream. |
IRootStorage | Switches the underlying disk file where objects are saved. |
IStorage | Instantiates a directory-like collection of storage and stream objects. |
IStream | Manipulates the underlying bytes of data that comprise an IStorage object. |
Drag and Drop | |
Interface | Purpose |
IDropSource | Provides feedback and status information (for example, key state) to applications implementing drag-and-drop. |
IDropTarget | Implemented to communicate status (for example, key state and mouse location) with the drop source by applications that support dropped data. |
In-Place Activation | |
Interface | Purpose |
IOleWindow | 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 Container | Used to enumerate connection points for event dispatching. |
IConnectionPoint | Specifies a dispatch point for an event. |