Component Object Model

From Free net encyclopedia

Component Object Model (COM) is a Microsoft platform for software componentry introduced by Microsoft in 1993. It is used to enable interprocess communication and dynamic object creation in any programming language that supports the technology. The term COM is often used in the software development world as an umbrella term that encompasses the OLE, OLE Automation, ActiveX, COM+ and DCOM technologies. Although COM was introduced in 1993, Microsoft did not begin emphasizing the name COM until 1997.

Although it has been implemented on several platforms, it is primarily used with Microsoft Windows. COM is expected to be replaced to at least some extent by the Microsoft .NET framework.

Contents

History

Anthony Williams, one of the more notable thinkers involved in the creation of the COM architecture, distributed a couple of internal papers in Microsoft that embraced the concept of software components; Object Architecture: Dealing With the Unknown - or - Type Safety in a Dynamically Extensible Class in 1988 and On Inheritance: What It Means and How To Use It in 1990. These provided the foundation of many, if not all, of the ideas behind the basics of COM.

From many of these ideas spawned Microsoft's first object-based framework, OLE, which is short for Object Linking and Embedding. OLE was built on top of dynamic data exchange (DDE) and designed specifically for compound documents. It was introduced with Word for Windows and Excel in 1991, and was later included with Windows, starting with version 3.1 in 1992. An example of a compound document is a spreadsheet embedded in a Word for Windows document; as changes are made to the spreadsheet within Excel, they appear automatically inside the Word document.

In 1991, Microsoft introduced Visual Basic Extensions (VBX) with Visual Basic 1.0.

In 1993, Microsoft released OLE 2 with COM as the underlying object model. While OLE 1 was focused on compound documents, COM and OLE 2 were designed to address software components in general. In 1994 OLE controls (OCX) were introduced as the successor to VBX controls. At the same time, Microsoft stated that OLE 2 would just be known as "OLE", and that OLE was no longer an acronym, but a name for all of the company's component technologies.

Later, in early 1996, Microsoft renamed some parts of OLE relating to the Internet ActiveX, and gradually renamed all OLE technologies to ActiveX, except the compound document technology that was used in Microsoft Office. Later that year, DCOM was introduced as an answer to CORBA.

Related technologies

COM was the major software development platform for Windows and, as such, influenced development of a number of supporting technologies.

COM+

With Windows 2000, a significant extension to COM named COM+ was introduced. At the same time, Microsoft de-emphasized DCOM as a separate entity. Transactional COM components formerly deployed on Windows NT4 using the Microsoft Transaction Server application interface were now handled more directly by the added layer of COM+. COM+ components were now added through the Component Services application interface.

An advantage of COM+ was that it could be run in "component farms". A component, if coded properly, could be reused by new calls to its initializing routine without unloading it from memory. Components could also be distributed (called from another machine) as was previously only possible with DCOM.

DCOM

Main article: Distributed component object model

.NET

The COM platform has largely been superseded by the Microsoft .NET initiative, and Microsoft now focuses its marketing efforts on .NET. To some extent, COM is now deprecated in favour of .NET. Despite this, COM remains a viable technology with an important software base – for example the popular DirectX 3D rendering SDK is based on COM. Also, a COM component should theoretically always have better performance than a matching managed .NET component. As of this writing, Microsoft has no plans for discontinuing either COM or support for COM.

Several of the services that COM+ provides, such as transactions and queued components, are still important for enterprise .NET applications.

There is limited support for backward compatibility. A COM object may be used in .NET by implementing a runtime callable wrapper (RCW). [1] .NET objects that conform to certain interface restrictions may be used in COM objects by calling a COM callable wrapper (CCW). [2] From both the COM and .NET sides, objects using the other technology appear as native objects.

.NET's remoting model solves a number of COM's remote execution shortcomings, allowing objects to be transparently marshalled by reference or value across process or machine boundaries.

Internet Security

The embedding of COM into the Internet Explorer web browser (under the name of ActiveX) created a combination of problems that has led to an explosion of computer virus, trojan and spyware infections. These malware attacks mostly depend on ActiveX for their activation and propagation to other computers. Microsoft recognized the problem with ActiveX as far back as 1996 when Charles Fitzgerald, program manager of Microsoft's Java team said "If you want security on the 'Net', unplug your computer. ... We never made the claim up front that ActiveX is intrinsically secure." [3]

As COM and ActiveX components are run as native code on the user's machine, there are fewer restrictions on what the code can do. Many of these problems have been addressed by the relative deprecation of COM in platforms developed since such as the Java platform, and later by the .NET platform as well.

Technical details

COM programmers build their software using COM-aware components. Different component types are identified by class IDs (CLSIDs), which are Globally Unique Identifiers, or GUIDs. Each COM component exposes its functionality through one or more interfaces. The different interfaces supported by a component are distinguished from each other using interface IDs (IIDs), which are also GUIDs.

COM interfaces have bindings in several languages, such as C, [[C++]], Visual Basic, and several of the scripting languages implemented on the Windows platform. All access to components is done through the methods of the interfaces. This allows techniques such as inter-process, or even inter-computer programming (the latter using the support of DCOM).

Interfaces

All COM components must (at the very least) implement the standard IUnknown interface. Indeed, all COM interfaces are derived from the IUnknown interface. The IUnknown interface consists of three methods: AddRef() and Release(), which implement reference counting and controls the lifetime of interfaces; and QueryInterface(), which by specifying an IID allows a caller to retrieve references to the different interfaces the component implements. The effect of QueryInterface() is similar to dynamic_cast<> in [[C++]] or casts in D, Java and C#.

A COM component's interfaces are required to exhibit the reflexive, symmetric, and transitive properties. The reflexive property refers to the ability for the QueryInterface() call on a given interface with the interface's ID to return the same instance of the object. The symmetric property requires that when interface B is retrieved from interface A via QueryInterface(), interface A is retrievable from interface B as well. The transitive property is similar to the symmetric property, but requires that if interface B is obtainable from interface A and interface C is obtainable from interface B, then interface C should be retrievable from interface A.

An interface consists of a pointer to a virtual function table that contains a list of pointers to the functions that implement the functions declared in the interface, in the same order that they are declared in the interface. This technique of passing structures of function pointers is very similar to the one used by OLE 1.0 to communicate with its system libraries.

COM specifies many other standard interfaces used to allow inter-component communication. For example, one such interface is IStream, which is exposed by components that have data stream semantics (e.g. a FileStream component used to read or write files). It has the expected Read and Write methods to perform stream reads and writes. Another standard interface is IOleObject, which is exposed by components that expect to be linked or embedded into a container. IOleObject contains methods that allow callers to determine the size of the component's bounding rectangle, whether the component supports operations like 'Open', 'Save' and so on.

Classes

A class in COM is referred to as a coclass. A coclass is COM's (language-independent) way of defining a class (in the object-oriented sense).

A coclass supplies concrete implementation(s) of one or more interfaces. In COM, such concrete implementations can be written in any programming language that supports COM component development, e.g. C++, Visual Basic, etc.

One of COM's major contributions to the world of Windows Development is the awareness of the concept of : separation of interface from implementation. This awareness has no doubt influenced the way programmers build systems today. An extension of this fundamental concept is the notion of : one interface, multiple implementations. By this, we mean that at runtime, an application can choose to instantiate an interface from one of many different concrete implementations.

Interface Definition Language and Type Libraries

As mentioned earlier, type libraries contain metadata that represent COM types. However, these types must first be described using the text-based Interface Definition Language (IDL). After that, the IDL contents are saved into a text file ready to be compiled (more on this later).

This is the common practice in the development of a COM component, i.e. to start with the definition of types using IDL. An IDL file is what COM provides that allows developers to define object-oriented classes, interfaces, structures, enumerations and other user-defined types in a language independent manner.

An IDL file is compiled by the MIDL compiler into a type library (.TLB file). The binary metadata contained within the type library is meant to be processed by various language compilers (e.g. VB, VC++, Delphi, etc). The end result of such .TLB processing is that the specific language compiler produces the language-specific constructs (VB classes for VB, C++ classes, various structs, macros and typedefs for VC++, etc) that represent the coclass defined in the .TLB (and ultimately that which was defined in the originating IDL file).

COM as an Object Framework

The fundamental principles of COM have their roots in Object-Oriented philosophies. It is a platform for the realization of Object-Oriented Development and Deployment.

Because COM is a runtime framework, types have to be individually identifiable and specifiable at runtime. To achieve this, globally unique identifiers (GUIDs) are used. Each COM type is designated its own GUID for identification at runtime (viz compile time).

In order that information on COM types be accessible at both compile time and runtime, COM presents the type library. It is through the effective use of type libraries that COM achieves its capabilities as a dynamic framework for the interaction of objects.

Consider the following example coclass definition in an IDL :

coclass MyObject 
{ 
  [default] interface IMyObject; 
  [default, source] dispinterface _IMyObjectEvents; 
};

The above code fragment declares a COM class named MyObject which must implement an interface named IMyObject and which supports (not implement) the event interface _IMyObjectEvents.

Ignoring the event interface bit, this is conceptually equivalent to defining a C++ class like this :

class CSomeObject : public ISomeInterface 
{ 
  ... 
  ... 
  ... 
};

where ISomeInterface is a C++ virtual class.

Referring once again to the MyObject COM class : once a coclass definition for it has been formalized in an IDL, and a Type Library compiled from it, the onus is on the individual language compiler to read and appropriately interpret this Type Library and then produce whatever code (in the specific compiler's language) necessary for a developer to implement and ultimately produce the binary executable code which can be deemed by COM to be of coclass MyObject.

Once an implementation of a COM coclass is built and is available in the system, next comes the question of how to instantiate it. Now, in languages like C++, we can use the CoCreateInstance() API in which we specify the CLSID (CLSID_MyObject) of the coclass as well as the interface (specified by the IID IID_IMyObject) from that coclass that we want to use to interact with that coclass. Calling CoCreateInstance() like this :

CoCreateInstance 
( 
  CLSID_MyObject, 
  NULL, 
  CLSCTX_INPROC_SERVER, 
  IID_IMyObject, 
  (void**)&m_pIMyObject 
);

is conceptually equivalent to the following C++ code :

ISomeInterface* pISomeInterface = NULL; 
pISomeInterface = new CSomeObject();

In the first case, we are saying to the COM sub-system that we want to obtain a pointer to an object that implements the IMyObject interface and we want coclass CLSID_MyObject's particular implementation of this interface. In the second case, we are saying that we want to create an instance of a C++ class that implements the interface ISomeInterface and we are using CSomeObject as that C++ class.

The equivalence should be clear. A coclass, then, is an object-oriented class in the COM world. The main feature of the coclass is that it is (1) binary in nature and consequently (2) programming language-independent.

Registry

In Windows, COM classes, interfaces and type libraries are listed by GUIDs in the registry. The COM libraries use the registry to locate either the correct local libraries for each COM object or the network location for a remote service.

Reference Counting

The COM specifications require a technique called reference counting to ensure that individual objects remain alive as long as there are clients which have acquired access to one or more of its interfaces and, conversely, that the same object is properly disposed of when all code that used the object have finished with it and no longer require it. Note that a COM object is responsible for freeing its own memory once its reference count drops to zero.

The most fundamental COM interface of all, i.e. IUnknown (from which all COM interfaces must be based), supports the concept of reference counting by including the following two methods : AddRef() and Release(). Note well that when we speak of reference counting, we are actually referring to the reference count of a COM Object, not the reference count of a COM Interface.

A COM Object will internally maintain only one integer value that is used for reference counting. When AddRef() is called via the object's IUnknown or some other COM interface, this interger value gets incremented. When Release() is called, this integer gets decremented. AddRef() and Release() are the only means by which a client of a COM object is able to influence its lifetime. The internal integer value remains a private member of the COM object and will never be directly accessible.

The purpose of AddRef() is to indicate to the COM object that an additional reference to itself has been effected and hence it is necessary to remain alive as long as this reference is still valid. Conversely, the purpose of Release() is to indicate to the COM object that a client (or a part of the client's code) has no further need for it and hence if this reference count has dropped to zero, it may be time to destroy itself.

Certain languages (e.g. Visual Basic) provides automatic reference counting so that COM object developers need not explicitly maintain any internal reference counter in their source codes. With other lower-level languages like C++, the declaration and use of an integer member in source code (used explicitly for reference counting) is necessary.

The following is a general guideline calling AddRef() and Release() to facilitate proper reference counting in COM object :

  • Functions (whether object methods or global functions) that return interface references (via return value or via "out" parameter) should increment the reference count of the underlyling object before returning. Hence internally within the function or method, AddRef() is called on the interface reference (to be returned). An example of this is the QueryInterface() method of the IUnknown interface. Hence it is imperative that developers be aware that the returned interface reference has already been reference count incremented and not call AddRef() on the returned interface reference yet another time.
  • Release() must be called on an interface reference when that interface reference is no longer required.
  • If a copy is made on an interface reference, AddRef() should be called on the interface reference. This is rather self-explanatory. After all, in this case, we are actually creating another reference on the underlying object.

Even though the principles of reference counting may be simple, explicit reference counting maintenance in source codes is often error prone in practice especially for new COM developers. This can lead to incorrect reference count situations in which a COM object remains alive even when no client uses it, leading to memory leakage. The other (even more undesireable) situation is where a client makes a method call on a COM object which has already destroyed itself, leading to runtime errors.

To alleviate this highly problematic situation, Microsoft introduced ATL (Active Template Library) for C++ developers. ATL provides for a higher-level COM development paradigm. It also shields COM client application developers from the need to directly maintain reference counting by providing smart pointer objects.

Instantiation

COM standardizes the instantiation (i.e. creation) process of COM objects by requiring the use of Class Factories. In order for a COM object to be created, two associated items must exist :

  • A Class ID.
  • A Class Factory.

Each COM Object must be associated with a unique Class ID (a GUID). Each COM Object must also be associated with its own Class Factory. A Class Factory is itself a COM object. It is an object that must expose the IClassFactory interface. The responsibility of such an object is to create other objects.

A class factory object is usually contained within the same executable code (i.e. the server code) as the COM object itself. The source codes of the class factory object may be written by the COM object developer himself/herself but it may also be emmited by a code generator program (e.g. the Visual Basic compiler). When a class factory is called upon to create a target object, this target object's class id must be provided. This is how the class factory knows which class of object to instantiate.

Take note that a single class factory object may create more than one class of objects. That is, two objects of different class ids may be created by the same class factory object. However, this is an internal detail which is of concern only to the COM object and class factory developer. It is transparent to the COM system.

Why do we need class factories ? By delegating the responsibility of object creation into a separate object, the developer is given greater flexibility in terms of object creation. For example singleton objects can be facilitated easily by a class factory.

In order that client applications are able to acquire class factory objects, COM servers must properly expose them. On this note, a class factory is exposed differently depending on the nature of the server code. A server which is DLL-based must export a DllGetClassObject() global function. A server which is EXE-based does not export any API. Rather, it registers the class factory at runtime via the CoRegisterClassObject() API.

The following is a general outline of the sequence of object creation via its class factory :

1. The object's class factory is obtained via the CoGetClassObject() API (a standard Windows API).

    • As part of the call to CoGetClassObject(), the Class ID of the object (to be created) must be supplied. The following C++ code demonstrates this :
  IClassFactory* pIClassFactory = NULL;

  CoGetClassObject
  (
    CLSID_SomeObject,
    CLSCTX_ALL,
    NULL,
    IID_IClassFactory,
    (LPVOID*)&pIClassFactory
  );
    • The above code indicates that the Class Factory object of a COM object, which is identified by the class id CLSID_SomeObject, is required. This class factory object is returned by way of its IClassFactory interface.

2. The returned class factory object is then requested to create an instance of the originally intended COM object. The following C++ code demonstrates this :

  ISomeObject* pISomeObject = NULL;

  if (pIClassFactory)
  {
    pIClassFactory -> CreateInstance
    (
      NULL,
      IID_ISomeObject,
      (LPVOID*)&pISomeObject
    ); 

    pIClassFactory -> Release();

    pIClassFactory = NULL;
  } 
    • The above code indicates the use of the Class Factory object's CreateInstance() method to create an object which exposes an interface identified by the IID_ISomeObject GUID. A pointer to the ISomeObject interface of this object is returned. Also note that because the class factory object is itself a COM object, it needs to be released when it is no longer required (i.e. its Release() method must be called).

The above demonstrates, at the most basic level, the use of a class factory to instantiate an object. Higher level constructs are also available, some of which do not even involve direct use of the Windows APIs.

For example, the CoCreateInstance() API can be used by an application to directly create a COM object without acquiring the object's class factory. However, internaly, the CoCreateInstance() API itself will invoke the CoGetClassObject() API to obtain the object's class factory and then use the class factory's CreateInstance() method to create the COM object.

Visual Basic supplies the new keyword as well as the CreateObject() global function for object instantiation. These Visual Basic language constructs encapsulate the acquisition of the class factory object of the target object (via the CoGetClassObject() API) followed by the invokation of the IClassFactory::CreateInstance() method.

Other languages, e.g. PowerBuilder's PowerScript may also provide their own high-level object creation constructs. However, CoGetClassObject() and the IClassFactory interface remain the most fundamental object creation technique.

Reflection

Components can describe themselves using COM Type Libraries. A type library contains information such as the CLSID of a component, the IIDs of the interfaces the component implements, and descriptions of each of the methods of those interfaces. Type libraries are typically used by RAD environments such as Visual Basic or Visual Studio to assist in the programming of COM components.

Programming

COM programmers are responsible for entering and leaving the COM environment, instantiating and reference counting COM objects, querying objects for version information, coding to take advantage of advanced object versions, and coding graceful degradation of function when newer versions aren't available.

Application and Network Transparency

COM objects may be instantiated and referenced from within a process, across process boundaries within a computer, and across a network, using the DCOM technology. Out-of-process and remote objects may use marshalling to send method calls and return values back and forth. The marshalling is invisible to the object and the code using the object.

Threading in COM

In COM, threading issues are addressed by a concept known as "apartment models". Here the term "apartment" refers to an execution context wherein a single thread or a group of threads is associated with one or more COM objects.

Apartments stipulate the following general guidelines for participating threads and objects:

  • Each COM object is associated with one and only one apartment. This is decided at the time the object is created at runtime. After this initial setup, the object remains in that apartment throughout its lifetime.
  • A COM thread (i.e., a thread in which COM objects are created or COM method calls are made) is also associated with an apartment. Like COM objects, the apartment with which a thread is associated is also decided at initialization time. Each COM thread also remains in their designated apartment until it terminates.
  • Threads and objects which belong to the same apartment are said to follow the same thread access rules. Method calls which are made inside the same apartment are performed directly without any assistance from COM.
  • Threads and objects from different apartments are said to play by different thread access rules. Method calls made across apartments are achieved via marshalling. This requires the use of proxies and stubs.

There are three types of Apartment Models in the COM world: Single-Threaded Apartment (STA), Multi-Threaded Apartment (MTA), and Neutral Apartment. Each apartment represents one mechanism whereby an object's internal state may be synchronized across multiple threads.

The Single-Threaded Apartment (STA) model is a very commonly used model. Here, a COM object stands in a position similar to a desktop application's user interface. In an STA model, a single thread is dedicated to drive an object's methods, i.e. a single thread is always used to execute the methods of the object. In such an arrangement, method calls are automatically queued by the system (via a standard Windows message queue) and there is no worry about race conditions or lack of synchronicity because each method call of an object is always executed to completion before another (even the same method) is invoked.

In case the COM object's methods perform their own synchronization, multiple threads dedicated to calling methods on the COM object are permitted. This is termed the Multiple Threaded Apartment (MTA). A process can consist of multiple COM objects, some of which may use STA and others of which may use MTA. The Thread Neutral Apartment allows different threads, none of which is necessarily dedicated to calling methods on the object, to make such calls. The only proviso is that all methods on the object must be serially reentrant.

Criticisms

Since COM has a fairly complex implementation, programmers can be distracted by some of the "plumbing" issues.

Environment Initialization

COM requires that the programmer call the CoInitialize[Ex] and CoUninitialize methods on each thread in the program that requires COM functionality. In addition, code using the OLE clipboard or drag and drop must call the OleInitialize and OleUninitialize methods. Since some threads in the system may be created by non-COM-aware libraries, the programmer must take care in using any of the COM library methods on a thread that was not created by the program itself.

Message pumping

When COM is initialized it creates a hidden window that is used for inter-thread and inter-process message routing. This window must have its message queue regularly pumped. On earlier versions of Windows failure to do so could cause system-wide deadlock. This problem is especially nasty because some Windows APIs initialize COM as part of their implementation, which causes a leak of implementation details.

Reference Counting

Reference counting within COM may cause problems if two or more objects are circularly referenced. The design of an application must take this into account so that objects are not left orphaned.

Objects may also be left with active reference counts if the COM "event sink" model is used. Since the object that fires the event needs a reference to the object reacting to the event, the objects reference count will never reach zero.

Reference cycles are typically broken using either out-of-band termination or split identities. In the out of band termination technique, an object exposes a method which, when called, forces it to drop its references to other objects, thereby breaking the cycle. In the split identity technique, a single implementation exposes two separate COM objects (also known as identities). This creates a weak reference between the COM objects, preventing a reference cycle.

DLL Hell

Because the location of each component is stored in a system-wide location (the Windows registry), there can be only one version of a certain component installed. Thus, COM seriously suffers from DLL hell, where two or more applications require different versions of the same component.

References

See also

External links

es:Component object model fr:Component Object Model hu:Component Object Model ja:Component Object Model pl:COM pt:ActiveX ru:COM fi:ActiveX sv:Component Object Model zh:组件对象模型