Professional Documents
Culture Documents
By Ken North
DBMS, June 1995
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.]
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.
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.
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.
Plug-In Components
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.
Distributed OLE
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 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.
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.
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-438-
6969 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-429-
7000, 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.
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
FIGURE 1.
--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.
FIGURE 2.
--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.
OLE (and its Component Object Model) provide a basis for application interoperation in
the Microsoft Windows environment.
1. Basic Concepts
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
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.
Unlike C++, where objects are defined using class definitions which generate user-
defined 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.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.
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.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
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.
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.
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 user-
defined 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).
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.
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
9.5 aggregates
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.3 Introspection
[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.