You are on page 1of 12

COM and OLE 14.

1 Introduction to COM Component Object Model (COM) specifies architecture, a binary standard, and a supporting infrastructure for building, using, and evolving component-based applications. It extends the benefits of object-oriented programming such as encapsulation, polymorphism, and software reuse to a dynamic and cross-process setting. Distributed COM (DCOM) is the distributed extension of COM. It specifies the additional infrastructure that is required to further extend the benefits to networked environments. Distributed computing is becoming a mainstream due to the advance in high-speed networking and the explosive growth of the Internet. Object-oriented programming has become a dominating programming paradigm for developing reusable software. Distributed objects combine the two trends and are becoming increasingly popular. More and more software systems are being built as distributed object applications and they often share a number of common goals. The features of COM include the separation of interfaces and implementations, support for objects with multiple interfaces, language neutrality, run-time binary software reuse, location transparency, architecture for extensibility, support for indirection, approach to versioning, and different styles of server lifetime management. The argument is that, by using COM/DCOM as a platform for building distributed object applications, researchers and developers can concentrate on important issues specific to their applications without having to devote a significant portion of their efforts to building the supporting infrastructure. 14.2 Object model The separation of interface and implementation is at the core of COM. An interface is a collection of functionally related abstract methods, and is identified by a 128-bit globally unique identifier (GUID) called the interface ID (IID). In contrast, an object class is a concrete implementation of one or more interfaces, and is also identified by a GUID called the class ID (CLSID). The use of GUIDs allows programmers to independently generate unique IDs without requiring central authority. An object instance (or object) is an instantiation of some object class. An object server is a dynamic link library (DLL) or an executable (EXE) capable of creating object instances of potentially multiple classes. A client is a process that invokes methods of an object. On Windows NT, a lot of COM-related information is stored in the registry under HKEY_CLASSES_ROOT. The information can be viewed and modified from the registry editor regedt32.exe. All registered object classes can be found under the CLSID key and all registered interfaces can be found under the Interface key. For example, "Microsoft Word Document" is an object class with CLSID {00020900-0000-0000-C000-000000000046} (or {00020906-00000000-C000-000000000046}). The LocalServer32 registry subkey indicates that its associated implementation filename is winword.exe. This class supports multiple interfaces including an IDataObject interface with IID {0000010E-0000-0000-C000-000000000046}. IDataObject is an interface enabling data transfer and notification of changes in data. It is also supported by the object class "Microsoft PowerPoint Slide" with CLSID {EA7BAE71-FB3B-11CD-A90300AA00510EA3} (or {64818D11-4F9B-11CF-86EA00AA00B929E8}) and implementation file PowerPnt.exe. 14.3 Binary interface standard COM specifies a binary standard for interfaces to interoperability of binary objects possibly built programming languages. Specifically, any COM satisfy two requirements. First, its instantiation ensure dynamic using different interface must must follow a

standard memory layout, which is the same as the C++ virtual function table. In other words, a COM interface pointer is a pointer to a pointer that points to an array of virtual function pointers. Second, any COM interface must inherit from the IUnknown interface so that its first three methods are (1) QueryInterface() for navigating between interfaces of the same object instance, (2) AddRef() for incrementing reference counts, and (3) Release() for decrementing reference counts. 14.4 Programming model A typical client/server interaction in COM goes like this: client starts the activation phase by calling CoCreateInstance() with the CLSID of the requested object and the IID of the requested interface. It gets back an interface pointer from the call. Upon returning the interface pointer, the object calls AddRef() on itself. In the method invocation phase, the client invokes methods of the interface through the pointer as if the object resides in its own address space. When the client needs to call methods of another interface of the same object, it calls QueryInterface() on the current interface and specifies the IID of the second interface. Once it gets back a pointer to the second interface, it can invoke methods as usual. When the client finishes using either interface pointer, it calls Release() on the pointer. 14.5 Remoting architecture We use the term remoting architecture to refer to the entire infrastructure that connects COM clients to out-of-process server objects. (See Figure 14.5.) The standard remoting architecture includes, among other things, (1) object proxies that act as the clientside representatives of server objects and connect directly to the client; (2) interface proxies that perform client-side data marshaling and are aggregated into object proxies; (3) client-side channel objects that use remote procedure calls (RPCs) to forward marshaled calls; (4) server-side endpoints that receive RPC requests; (5) server-side stub manager that dispatches calls to appropriate interface stubs; (6) interface stubs that perform server-side data marshaling and make actual calls on the objects; and (7) standard marshaler that marshals interface pointers into object references on the server side and unmarshals the object references on the client side. Note that interface proxies and stubs are application-specific and are generated by running an Interface Definition Language (IDL) compiler on application-supplied IDL files. The other objects are applicationindependent and are provided by COM.

Figure 14..5. COM remoting architecture and extensibility. 14.6 DCOM (Distributed COM)

The DCOM wire protocol extends the remoting architecture across different machines. Currently, it is specified as a set of extensions layered on top of the DCE RPC specification. It adopts DCE RPCs Network Data Representation (NDR) format for marshaling data to be transmitted across heterogeneous network. It also leverages DCE RPCs security capabilities for authentication, authorization, and message integrity. In addition, DCOM specifies the RPC interfaces for remote server activation, ID-to-endpoint resolution, remote IUnknown method invocation, and pinging for robust reference counting. It also defines the data structure of object references and the DCOM-specific portion of RPC packets. 14.7 Threading model If an application allows multiple clients to concurrently invoke methods of the same COM object, some synchronization mechanisms need to be provided to protect the data. COM introduces the concept of apartments to allow objects with different concurrency constraints to live in the same process. An apartment is a logical grouping of objects that share the same concurrency constraints. Before a thread can use COM, it must first enter an apartment by calling CoInitializeEx( ). Every COM process can have at most one multithreaded apartment (MTA), but it can contain multiple singlethreaded apartments (STAs). Multiple threads can execute in an MTA concurrently, so object data in an MTA need to be properly protected. In contrast, only one thread can execute in an STA and so concurrent accesses to objects in an STA are automatically serialized. 14.8 Main Features The main features of COM are transparency, extensibility, indirection, versioning, server life time management. 14.8.1 Transparency In the activation phase, COM supports both non-transparent and transparent modes. In the non-transparent mode, a client can explicitly specify whether the server component resides in a DLL or an EXE, and the remote machine name if the component is to be run remotely. Alternatively, the client can select the transparent mode and let COM consult the registry to determine such attributes. Once the machine name is determined, COM will try to attach to a running server instance, hosting the requested object class, on that machine. If none exists, COM will automatically locate the server implementation file, start a server process, and create an object instance. To provide call transparency in the method invocation phase, the server returns an object reference, instead of a physical connection, as the result of an activation request. An object reference encapsulates all the necessary connection information for the client to reach the server object. It typically includes machine IP address, port number, and object ID. Although a server usually returns an object reference representing an object instance that it is hosting, it can also return an object reference that it has obtained from another machine. When the object reference is shipped to the client side, the client-side COM infrastructure unmarshals it by extracting the connection information, making the connection, and returning an interface pointer to the client. When the client makes a call through the pointer, the call will be transparently routed to the object identified by the object reference, without passing through the server that was initially contacted. 14.8.2 Extensibility Usually, COM applications use standard marshaling and rely on COM to provide standard marshaling and transport. However, some

applications may need to customize the client-server connection for a number of reasons. For example, a client process may wish to cache read-only data to speed up access. An application may need to run RPC on a new transport, or may require multicast or asynchronous transport that does not fit the RPC paradigm. Some applications need to integrate DCOM with third-party compression or encryption packages. Distributed shared object systems may wish to hide data consistency logic in the proxies. All these applications demand extensibility in the remoting architecture. Extensibility provided by COM can be divided into three categories: below, above, and within. The first category extends COM at the RPC layer and below. The main advantage is that it is totally transparent to the standard remoting architecture. A disadvantage is that it is only applicable to transport replacement applications. Currently, DCOM can be configured to run on either TCP, UDP, IPX, or NetBIOS by simply modifying the registry key KEY_LOCAL_MACHINE\Software\Microsoft\Rpc. If a new transport, such as fast user-level networking, supports a compatible set of APIs, DCOM applications can be configured to run on the new transport without any changes. To achieve the other two types of extensibility, COM supports a custom marshaling mechanism. By implementing an IMarshal interface, a server object indicates that it wants to bypass the standard remoting architecture and supply its own custom connection. By implementing the method calls of the IMarshal interface, the object has the flexibility of creating any number of objects and connecting them in any way to serve as the custom remoting architecture. In particular, the object can construct a custom object reference and specify the CLSID of the client-side custom unmarshaler, which will be automatically instantiated by COM to receive and interpret the custom object reference and to make the custom connection. The second type of extensibility allows inserting a handler layer above the standard remoting architecture and below the user application. It is often called semi-custom marshaling because most of the tasks are eventually delegated to the standard remoting architecture. Specifically, the server objects IMarshal implementation delegates the task of marshaling to the standard marshaler, and performs additional processing on top of that. Similarly, the custom unmarshalers IMarshal implementation also delegates the task of unmarshaling to the standard marshaler and builds additional logic on top of that. As part of the marshaling/unmarshaling process, a custom proxy and a custom stub are inserted to allow additional processing of each method invocation. The third type of extensibility is the most general one. Very often, applications want to supply a few custom objects, while reusing most of the standard marshaling objects. For example, algorithms that manipulate marshaled data streams instead of individual call parameters may want to replace channel-level objects and reuse marshaling objects. This is hard to accomplish in current COM architecture. A new componentized architecture called COMERA (COM Extensible Remoting Architecture) has been proposed to promote binary software reuse at the infrastructure level. 14.8.3 Indirection Many software problems can be solved by one more level of indirection. Supporting indirection is a special form of providing extensibility. In most traditional programming paradigms, offering one more level of indirection often involves tricky programming hacks that may impose certain limitations. In contrast, COM builds into its architecture the support for indirection. As demonstrated in the following discussion, activation indirection can be used for on-

line software update and load balancing, while call indirection can facilitate fault tolerance and object migration. The first indirection occurs when a client requests the activation of a server object by specifying its CLSID. Since the mapping from a CLSID to the server executable filename is determined by registry keys such as TreatAs and LocalServer32, the same client can actually be invoking two different implementations with the same CLSID across two activations if a key value is changed. This provides the basis for on-line software update. Another indirection exists after the server receives the activation request and before it returns an interface pointer. As pointed out earlier, the server can potentially return an object reference that it has obtained from another machine. This provides the basis for load balancing. As part of the (interface pointer) marshaling and unmarshaling process, interface stubs and proxies responsible for data marshaling are dynamically loaded and created. Since these stubs and proxies are themselves COM objects, this process provides yet another indirection point. By controlling through the registry (1) the mapping from the interface IID to the CLSID of these proxy/stub objects, or (2) the mapping from such CLSID to the implementation filename, applications can choose to either reuse the standard interface proxies and stubs or supply their own custom ones. In standard marshaling, once an interface pointer is returned to the client, the client is bound to the server object and so it is in general harder to provide indirection for method calls. A limited form of indirection is provided by the RPC layer: if the servers IP address is failed-over to another machine and the original connection is broken, the RPC layer will retry the connection and get redirected to the new machine. Custom marshaling provides the ultimate form of method call indirection. Basically, the entire remoting architecture can be considered as a built-in architecture for indirection. At the higher layer, custom proxies can perform client-side data-dependent routing by examining input parameters. At the lower layer, custom channels can dynamically decide on which physical connection to send a message through. This serves as the basis for client-transparent object migration and fault tolerance. The above approach supplies indirection logic inside the proxy and channel objects. Alternatively, since these objects are themselves COM objects that are dynamically created during the unmarshaling process, indirection can also be supported by either specifying different CLSIDs or changing the CLSID to filename mapping. 14.8.4 Versioning

COM supports a rich set of server styles that require different server lifetime management strategies. In the basic style, an activated server creates class factories for all supported CLSIDs. Object instances are created when clients make such requests. Reference counting is used to manage server lifetime: the server increments the count upon returning an interface pointer. Each client must follow a set of rules to increment the count (when duplicating an interface pointer, for example) and to decrement the count when finish using a pointer. When the count drops to zero, the server object realizes that it is no longer serving any client and can therefore be destroyed. To solve the problem of an abnormally terminated client holding a reference indefinitely, COM provides an automatic pinging mechanism : the client-side ping code starts sending periodic heartbeat messages to the server after an object reference is unmarshaled. It stops sending the heartbeats when the client process terminates. Upon detecting that the number of missing heartbeats has exceeded a threshold, the server-side ping code declares that the client has died and so decrements the server objects reference count on the clients behalf. Long-running, singleton objects are another style of implementing COM servers. A server process creates a set of object instances upon starting and destroys them upon exiting. Since there is no class factory, clients cannot use the usual CoCreateInstance() call. Instead, they use CoGetClassObject() to connect to these shared server objects directly. Since the server is long-running and the objects lifetime is determined by the server process lifetime, reference counting and pinging are turned off. Microsoft Transaction Server (MTS) provides yet another style of server programming. MTS server objects must be implemented in the form of DLLs that are to be hosted by MTS surrogate processes. MTS provides context objects for these server objects so that they can participate in transactions. But MTS is more than transactions. It also provides efficient resource management. In the MTS style, a server object instance notifies MTS when it has finished processing for current task and no longer needs the state associated with the instance. MTS can then reuse that instance and its associated resources to serve other clients without incurring object activation and resource initialization overhead. 14.9 A Simple application of COM In this section you will create a simple Inprocess server without the help of COM object building wizard available in visual studio. This exercise will give you exposure in to the fundamentals of COM. 1. Create an empty dll project of type Win32 Dynamic Link Library by the two steps of the appwizard shown in figure 14.9(a) and figure 14.9(b) and add the files one by one as instructed in the forthcoming steps.

COMs approach to versioning is based on the following three requirements: first, any interface (identified by an IID) must be immutable. Second, a new implementation of the same CLSID must support existing interfaces. Finally, any client must start interacting with a server by querying an interface with an IID. Such a combination allows independent evolution of client and server software. Suppose, on a particular machine, the server software is upgraded before the client is. Since the new server supports all old interfaces, the old client can still obtain all the interface pointers that it needs and run smoothly. When the client software is also upgraded, the new client will query the new interfaces to enjoy the new features. In contrast, suppose the client software is upgraded first on another machine. The new client will try querying the new interfaces on the old server and fail. This procedure forces the new client to handle the failure by, for example, providing only old features. But it will not cause the new client to crash or unknowingly execute incorrectly. 14.8.5 Server lifetime management

Figure 14.9(a) Creating a Win32 dynamic link library project

float _stdcall CalcArea(float w,float h); private: void _stdcall GetBackPointer(XSArea *pBack); XSArea *pBack; }m_xRectArea; class XSquare:public ISquare { friend class XSArea; public: HRESULT _stdcall QueryInterface(REFIID iid,void **ppvObj); ULONG _stdcall AddRef(); ULONG _stdcall Release(); float _stdcall CalcArea(float w); private: void _stdcall GetBackPointer(XSArea *pBack); XSArea *pBack; }m_xSquareArea; static int numComp;//component count. private: int nRefCount;//interface count. };//End component class The interfaces are implemented as nested classes in the coclass. AddRef and Release methods are defined for both the coclass and the interface classes. However as you will see later their implementation will be delegated to the outer coclass. The other important addition to the nested class is the method called GetBackPointer that basically allows the nested class to obtain a pointer to the outer coclass. Note also that the outer coclass(XSArea) is the friend of the nested classes so that it can access the private(and also protected) members of the nested classes. 4. Implement the coclass in a file called component.cpp

Figure 14.9(b) Creating a Win32 dynamic link library project 2. Create the interface

The server consists of one class that exposes two interfaces that provide service for computing area of a rectangle and a square. Declare the interface in a file called interface.h. The file is shown below. //Two simple interfaces //interface.h struct IRectangle:public IUnknown { virtual float _stdcall CalcArea(float w, float h)=0; }; struct ISquare : public IUnknown { virtual float _stdcall CalcArea(float w)=0; }; As you can see the two interfaces are derived from IUknown. Thus, the three virtual functions of IUknown namely, AddRef, Release and QueryInterface are automatically inherited. The method CalcArea computes the area of a rectangle and square. Note that the methods are pure virtual and will be implemented later. Note that the interface method returns a float value of the computed area although strict COM guidelines dictate that all interface methods must return HRESULT parameter which should be tested for success or failure. The computed area in this case has to be fetched by means of a output pointer variable. 3. Define the component class in a file called component.h

The coclass implements the methods of the interfaces in addition to its own methods. The implementation file is shown below. //component.cpp //coclass implementation(component.cpp) #include <objbase.h> #include "guiddef.h" #include "interface.h" #include "component.h" #include "shapearea.h" XSArea::XSArea() { //Make it accessible to the embedded classes m_xRectArea.GetBackPointer (this); m_xSquareArea.GetBackPointer (this); numComp++; nRefCount=0; } ULONG XSArea::AddRef () { nRefCount++; return nRefCount; } ULONG XSArea::Release () { nRefCount=nRefCount-1; if(nRefCount==0) { delete this;

The COM object is represented by the single coclass you will create. The class definition is given below. //component.h class XSArea { public: XSArea(); ULONG AddRef(); ULONG Release(); //Nested implementation of the interface class XRectangle:public IRectangle { friend class XSArea; public: HRESULT _stdcall QueryInterface(REFIID iid,void **ppvObj); ULONG _stdcall AddRef(); ULONG _stdcall Release();

numComp=numComp-1; return 0; } return nRefCount; } ULONG XSArea::XRectangle ::AddRef () { //Let the component keep track of total interface //count return pBack->AddRef(); } ULONG XSArea::XRectangle ::Release () { return pBack->Release(); } HRESULT XSArea::XRectangle ::QueryInterface (REFIID iid ,void **ppvObj) { if (iid==IID_IUnknown) { *ppvObj=&(pBack->m_xRectArea); } else if(iid==IID_IRectangle) { *ppvObj=&(pBack->m_xRectArea ); } else if(iid==IID_ISquare) { *ppvObj=&(pBack->m_xSquareArea); } else { *ppvObj=NULL; return E_NOINTERFACE; } AddRef(); return S_OK; } float XSArea::XRectangle ::CalcArea (float w,float h) { return(w*h); } void XSArea::XRectangle ::GetBackPointer (XSArea *ptr) { pBack=ptr; } ULONG XSArea::XSquare ::AddRef () { //Let the component keep track of totsl interface //count return pBack->AddRef (); } ULONG XSArea::XSquare ::Release () { return pBack->Release (); } HRESULT XSArea::XSquare::QueryInterface (REFIID iid ,void **ppvObj) { if (iid==IID_IUnknown) { *ppvObj=&(pBack->m_xRectArea); } else if(iid==IID_IRectangle) { *ppvObj=&(pBack->m_xRectArea ); } else if(iid==IID_ISquare)

{ *ppvObj=&(pBack->m_xSquareArea); } else { *ppvObj=NULL; return E_NOINTERFACE; } AddRef(); return S_OK; } float XSArea::XSquare ::CalcArea (float w) { return(w*w); } void XSArea::XSquare ::GetBackPointer (XSArea *ptr) { pBack=ptr; } The constructor for the coclass passes a pointer of the coclass(this pointer) to the embedded class by invoking their methods GetBackPointer. The function GetBackPointer simply stores the pointer to the coclass in the member variable pBack of the embedded class. The constructor also increments the numComp variable for every construction of the object. The composite interface count is initialized to 0. The coclass implements its own AddRef and Release methods. The two interfaces AddRef and Release methods simply delegate to the coclass methods. The QueryInterface methods are implemented only for the interfaces. The first argument to this function is an interface id and the second argument is an output variable to get a pointer to the coclass. Note that the id IID_IUnknown is used to get a pointer to the IRectangle interface. It could have been implemented to get a pointer to ISquare as well. After getting a pointer to the desired interface the function increments the composite interface count by invoking AddRef function. If the interface id does not match an existing one then the function returns E_NOINTERFACE. The CalArea functions are trivial. As said before these functions should return any result by means of pointer arguments to the function rather than any returned value from the function as is done here. 5. Create a file (guiddef.h) containing the GUID s for the interfaces and the coclass

The GUIDGEN utility is provided under windows 2000,NT and the higher versions to create globally unique identifiers for COM classes and the interfaces it contains. Start the GUIDGEN application from the start menu of your machine. The application interface is shown in figure 14.9(c ).

Figure 14.9(c) Running instance of GUIDGEN utility

Click the New GUID button and then copy the GUID to clip board by clicking Copy button. Open a text file (guiddef.h) and copy the contents from clip board. Create three entries in the file by repeating this step. Modify the file as shown below. //guiddef.h //GUIDs for coclass and the interfaces // {9B2C98D5-1203-4da8-A382-A07E4CF43EB3} static const GUID IID_IShape = { 0x9b2c98d5, 0x1203, 0x4da8, { 0xa3, 0x82, 0xa0, 0x7e, 0x4c, 0xf4, 0x3e, 0xb3 } }; // {B61A64E7-EB4E-4b45-983C-CFE398A95E5F} static const GUID IID_IRectangle = { 0xb61a64e7, 0xeb4e, 0x4b45, { 0x98, 0x3c, 0xcf, 0xe3, 0x98, 0xa9, 0x5e, 0x5f } }; // {05767E05-B2C2-4fdf-A37F-417B3EC813A8} static const GUID IID_ISquare = { 0x5767e05, 0xb2c2, 0x4fdf, { 0xa3, 0x7f, 0x41, 0x7b, 0x3e, 0xc8, 0x13, 0xa8 } }; 6. Create a file (classfactory.h) that contains a declaration for the class factory for the component. The class declaration is shown below.

InterlockedDecrement(&nRefCount); if(nRefCount==0) { delete this; return 0; } return nRefCount; } HRESULT CShapeFactory::QueryInterface (REFIID iid,void **ppvObj) { if(iid==IID_IUnknown||iid==IID_IClassFactory) { *ppvObj=this; } else { *ppvObj=NULL; return E_NOINTERFACE; } AddRef(); return S_OK; } HRESULT CShapeFactory::CreateInstance (IUnknown *punk,REFIID riid,void **ppvObj) { XSArea *ptrArea=new XSArea; //We use the IRectangle interface to obtain a pointer to it HRESULT hResult=ptrArea>m_xRectArea.QueryInterface(riid,ppvObj); return hResult; } HRESULT CShapeFactory ::LockServer(BOOL will_lock) { if(will_lock) { numLocks++; } else { numLocks--; } return S_OK; } The function QueryInterface is used by COM library to obtain a pointer to class factory. Subsequently CreateInstance function is used to create an instance of the class factory object on the heap. Other methods have their usual meaning. The functions InterlockedIncrement and InterlockedDecrement are used by AddRef and Release methods to increment and decrement the interface count. 8. Create the dll implementation file (shapearea.cpp) and its header file (shapearea.h)

//classfactory.h // Class factory for the component class CShapeFactory :public IClassFactory { public: CShapeFactory() {nRefCount=0;} HRESULT _stdcall QueryInterface(REFIID iid,void **ppvObj); ULONG _stdcall AddRef(); ULONG _stdcall Release(); HRESULT _stdcall CreateInstance(IUnknown *punk,REFIID riid,void **ppvObj); HRESULT _stdcall LockServer(BOOL ILock); static int numLocks; private: long nRefCount; }; The class factory is responsible for creating an instance of the COM object. The COM library invokes QueryInterface from the exported function DllGetClassObject (shown in step 7). The static variable numLocks keeps a count of locked component. A lock count greater than zero keeps the COM object in memory. The LockServer member function is used to increment the lock count. 7. Implement the class factory in the file classfactory.cpp

//classfactory.cpp //Class factory implementation #include <objbase.h> #include "interface.h" #include "component.h" #include "classfactory.h" ULONG CShapeFactory::AddRef() { return InterlockedIncrement(&nRefCount); } ULONG CShapeFactory::Release () {

This file contains the main entry point function for the dll and also the exported functions for registering the server in the registry database. Besides, this also exports the DllGetClassObject function that is used by the COM library to create an instance of the COM object via the class factory. The implementation file (shapearea.cpp) is shown below. //shapearea.cpp //Dll implementation file #include <windows.h> #include <windowsx.h> #include <stdio.h> #include "interface.h"

#include "component.h" #include "classfactory.h" #include "shapearea.h" #include "regupdate.h" #include "guiddef.h" //Global definition of static variables. int XSArea::numComp=0; int CShapeFactory::numLocks =0; static HINSTANCE g_hinstance; //Dll entry point BOOL WINAPI DllMain(HINSTANCE hInstance,DWORD fdwReason,LPVOID lpReserved) { switch(fdwReason) { //the calling process loads the dll through the library. //Save the instance handle created by library in a global case DLL_PROCESS_ATTACH: g_hinstance=hInstance; break; } return TRUE; } STDAPI DllRegisterServer(void) { WCHAR wszCLSID[72]; TCHAR szCLSID[72]; TCHAR szEntry[72]; TCHAR szProgID[]="AreaCal.Obj"; TCHAR szFileName[256]; StringFromGUID2(IID_IShape,wszCLSID,40); WideCharToMultiByte(CP_ACP,0,wszCLSID,1,szCLSID,40,NULL,FALSE); sprintf(szEntry,"%s",szProgID); EnterRegistryKey(szEntry,"",szProgID); sprintf(szEntry,"%s\\CLSID",szProgID); EnterRegistryKey(szEntry,"",szCLSID); sprintf(szEntry,"CLSID\\%s",szCLSID); EnterRegistryKey(szEntry,"",szProgID); sprintf(szEntry,"CLSID\\%s\\ProgID",szCLSID); EnterRegistryKey(szEntry,"",szProgID); GetModuleFileName(g_hinstance,szFileName,256); sprintf(szEntry,"CLSID\\%s\\InProcServer32",szCLSID); EnterRegistryKey(szEntry,"",szFileName); return 0; } STDAPI DllUnregisterServer(void) { WCHAR wszCLSID[72]; TCHAR szCLSID[72]; TCHAR szEntry[72]; TCHAR szProgID[]="AreaCal.Obj"; StringFromGUID2(IID_IShape,wszCLSID,40); WideCharToMultiByte(CP_ACP,0,wszCLSID,1,szCLSID,40,NULL,FALSE); sprintf(szEntry,"%s",szProgID); DeleteRegistryKey(szEntry); DeleteRegistryKey(szProgID); return 0;

} STDAPI DllCanUnloadNow() { //called to determine if the server module can be unloaded if(CShapeFactory::numLocks>0||XSArea::numComp>0) return S_FALSE; else return S_OK; } STDAPI DllGetClassObject(REFCLSID clsid,REFIID iid,void **ppv) { CShapeFactory *ptrshape; HRESULT hr=E_OUTOFMEMORY; if(clsid!=IID_IShape) return E_FAIL; ptrshape=new CShapeFactory; if(ptrshape==NULL) return hr; //Now query the interface requested hr=ptrshape->QueryInterface (iid,ppv); if(FAILED(hr)) delete ptrshape; return hr; } The header file containing the prototypes for the exported functions of the file above is shown below. //shapearea.h //Implementation header file (shapearea.h) STDAPI DllCanUnloadNow(); STDAPI DllGetClassObject(REFCLSID clsid,REFIID iid, void **ppv); STDAPI DllRegisterServer(); STDAPI DllRegisterServer(); STDAPI DllUnregisterServer(); The DllMain is the entry point for the dll server. This minimal function simply stores the handle to the dll module in the static global variable g_hinstance. The HINSTANCE of a dll is the same as the HMODULE of the dll. As you will see shortly, the g_hinstance variable can be used to obtain the dll file name by passing it to the function GetModuleFileName. The fdwReason can be DLL_PROCESS_ATTACH which indicates that the dll is being loaded in to the virtual address space of the current process either as a result of the process starting up or as a result of a call to LoadLibrary function. During this process the dll can allocate and initialize the dll data. The other fdwReason parameter can be DLL_PROCESS_DETACH . This indicates that the dll is unloading from the address space of the calling process. This can be checked to deallocate any allocated data. The other values for the fdwReason are DLL_THREAD_ATTACH and DLL_THREAD_DETACH which can be checked to see if the entry point function is invoked as a result of the calling creating a new thread or an existing thread of the calling process is exiting. The DllRegisterServer and DllUnregisterServer implemented and exported (see step 9 below) so that the regsvr32.exe utility can invoke these during registration of the server. The DllRegisterServer and DllUnregisterServer functions use our implementation of EnterRegistryKey and DeleteRegistryKey functions (see step 10 below). The first parameter to EnterRegistryKey is the key to be entered(string on the left side of the registry editor). You can open the registry database by the regedit.exe utility. The second argument is the string representing the name of the key. We pass empty string

for this argument as naming each key is not mandatory. The third argument is the string representing the value of the key. The figures 14.9(d) through 14.9(e) shows all the five keys after the server is registered by regsvr32.exe.

The implementation file (regupdate.cpp) is shown below. //regupdate.cpp //Registry update utility implementation (regupdate.cpp) #include <windows.h> #include <string.h> #include "regupdate.h" BOOL EnterRegistryKey(LPSTR regEntry,LPSTR regValName,LPSTR regValue) { LONG lResult; HKEY hKey; DWORD dwDisposition; //Add key to HKEY_CLASSES_ROOT lResult=RegCreateKeyEx(HKEY_CLASSES_ROOT,regEntry,0,N ULL,REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NUL L,&hKey,&dwDisposition); if(lResult!=ERROR_SUCCESS) return FALSE; //Now make sure that the value name and value are valid. //If so enter the value name/value pair else return. int nLength=strlen(regValue); if(regValue==NULL||regValName==NULL) return TRUE; lResult=RegSetValueEx(hKey,regValName,0,REG_SZ, (LPBYTE)regValue,nLength); RegCloseKey(hKey); return lResult; } BOOL DeleteRegistryKey(LPTSTR regValue) { HKEY hKey=NULL; LONG lResult; int nIndex=0; TCHAR regSubKey[40]; FILETIME file_time; DWORD nBuf; //open the key before deleting lResult=RegOpenKeyEx(HKEY_CLASSES_ROOT,regValue,0,KE Y_ALL_ACCESS,&hKey); if(lResult!=ERROR_SUCCESS) return FALSE;s //Now delete the subkeys

Figure 14.9(d) The two keys AreaCal.Obj and AreaCal.Obj\CLSID under HKEY_CLASSES_ROOT

Figure 14.9(e) The three keys under HKEY_CLASSES_ROOT\CLSID The other two functions exported by the dll are DllCanUnloadNow and DllGetClassObject. A call to DllCanUnloadNow determines if the dll exporting this function is in use. It returns S_OK if dll is no longer being used can be unloaded. It returns S_FALSE if the dll is being used and can not be unloaded. This function is called by the OLE library and you need not call it directly in your application. The DllGetClassObjectis called by the COM library from a call to CoGetClassObject for in-process servers. The first argumentto this function is the CLSID of the class object. The secong argument is a reference to the interface identifier that communicates with the class object. The third argument is a pointer that receives the interface pointer requested in the second argument. The function returns S_OK if the object was retrieved successfully. It returns CLASS_E_CLASSNOTAVAILABLE if the dll does not support the class. 9. Create a module definition file (shaparea.def) for the exported functions.

The module definition file is shown below. //shaparea.def LIBRARY comprgm.dll EXPORTS DllCanUnloadNow PRIVATE DllGetClassObject PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE 10. Create the implementation file and the header file for two registry update functions.

while(RegEnumKeyEx(hKey,nIndex,regSubKey,&nBuf,0,NULL,N ULL,&file_time)! =ERROR_NO_MORE_ITEMS)RegDeleteKey(hKey,regSubKey); //Noe delete the key whose subkeys are deleted RegDeleteKey(HKEY_CLASSES_ROOT,regValue); RegCloseKey(hKey); return TRUE; } The registry API functions used are shown in bold faces. The header file (regupdate.h) for the above implementation is shown below. //regupdate.h //Registry update implementation prototypes BOOL EnterRegistryKey(LPTSTR regEntry,LPTSTR name,LPTSTR val);

BOOL DeleteRegistryKey(LPTSTR regEntry); 11. Build the dll and Use the utility regsvr32.exe to register the dll. The regsvr32.exe can be started from the Run button of the Start menu under Windows 2000 or NT. The command line used to register the server under Windows 2000 is shown below. regsvr32 /s/n/i C:\Program Files\Microsoft Studio\MyProjects\comprgm\Debug\comprgm.dll At this point the server is ready to be used. Testing the server Now the COM server is created and registered you can write a simple client to test it. The client is a dialog based MFC application whose framework is created by using the appwizard. The relevant steps needed to test the functionality of the COM server is outlined. Visual

} Note that the variables clsid, pFact and punk need to be defined in the dialog implementation header file as shown below. class CTestComDlg : public CDialog { // Construction public: CTestComDlg(CWnd* pParent = NULL); standard constructor CLSID clsid; IClassFactory *pFact; IUnknown *punk; }; 3.

//

Use the class wizard and add button click message handlers for the four buttons(OK and Cancel created by default and two created by you) of the dialog. Modify the four functions as shown in the bold face below.

void CTestComDlg::OnRectArea() { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CDialog::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. // TODO: Add your control notification handler code here Figure 14.9(f) Dialog template of the client 1. Modify the header of the dialog class implementation file as shown in bold below. IRectangle *rRect; punk->QueryInterface (IID_IRectangle, (void**)&rRect); UpdateData(TRUE); m_ar=rRect->CalcArea(m_hr,m_wr); UpdateData(FALSE); rRect->Release(); } void CTestComDlg::OnSqArea() { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CDialog::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. // TODO: Add your control notification handler code here ISquare *rSqr; punk->QueryInterface(IID_ISquare,(void**)&rSqr); UpdateData(TRUE); m_as=rSqr->CalcArea(m_ws); UpdateData(FALSE); rSqr->Release(); } void CTestComDlg::OnOK() { // TODO: Add extra validation here punk->Release(); CoUninitialize(); CDialog::OnOK(); }

#include "stdafx.h" #include "TestCom.h" #include <objbase.h> #include "TestComDlg.h" #include "guiddef.h" #include "interface.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif Also include the header files guiddef.h and interface.h in the client program. 2. Modify the OnInitDialog function as shown in bold below.

BOOL CTestComDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here CoInitialize(NULL); HRESULT hResult=::CLSIDFromProgID(L"AreaCal.Obj",&clsid); CoGetClassObject(clsid,CLSCTX_INPROC_SERVER,NULL,II D_IClassFactory,(void**)&pFact); pFact->CreateInstance(NULL,IID_IUnknown,(void**)&punk); pFact->Release(); return TRUE; // return TRUE unless you set the focus to a control

void CTestComDlg::OnCancel() { // TODO: Add extra cleanup here punk->Release(); CoUninitialize(); CDialog::OnCancel(); } As the application starts the OnInitDialog initializes the COM library by a call to CoInitialize. The progid of the dll server(AreaCal.Obj) is used to retrieve the class id by a call to CLSIDFromProgID. Subsequently, a call to CoGetClassObject retrieves a pointer to the class factory object. Next, using the CreateInstance function of the class factory an instance of the COM object is created. Now, the class factory object is no longer needed and released by a call to Release member function. The CreateInstance function retrieves a pointer to one of the interfaces (we choose to retrieve the pointer to IRectArea) in the punk variable. By using the pointer to one of the interface the subsequent interface pointers are obtained in the button push handlers for the two area calculation buttons of the dialog (see figure14.9(g)). After the interface pointer is obtained, the edit control data are passed to the DDE variables by a call to the UpdateData. Now,the CalcArea function is invoked with the rectangle and the square dimensions to compute the area. After the area value is stored in the DDE variable for the display edit control, a call to UpdateData moves the value from the variable to the control. The implementation for OnOk and OnCancel basically decreases the interface count and then invokes CoUninitialize to uninitialize the COM library. The two functions OnOk and OnCancel then call the default implementation of OnOk and OnCancel which removes the dialog from view and terminate the client application. The running instance of the client application with computed areas for the rectangle and square is shown in figure 14.9(g).

you wish to implement more advanced features, such as OLE drag and drop, linked OLE objects, or OLE-based clipboard operations. 14.10.1 Methods and Memory Allocation Of particular interest when implementing methods is the issue of memory allocation. OLE defines specific rules for cases when it becomes necessary for the caller to pass a pointer to the called method, or for the method to return data in the form of a pointer to the caller. When memory is allocated by the caller, it must be freed by the caller. When memory is allocated by the method, it must be freed by the caller. The exception is error conditions; in such cases, the method must ensure that memory allocated by it will be reliably freed and furthermore, that all returned pointers are explicitly set to NULL as appropriate. When a pointer is passed to the method, the method may free the memory associated with it and reallocate. The caller is responsible for freeing the final value. However, if an error occurs, the method is responsible for releasing any memory allocations it made. OLE provides a memory allocation interface (the IMalloc interface) that provides thread-safe memory allocation methods. A pointer to this allocator can be obtained by calling the CoGetMalloc OLE function. 14.10.2 Inheritance and Reusing Objects Inheritance and reusability are terms with specific meanings for the developer of object-oriented code. These terms imply the capability of deriving your own classes from base classes, replacing methods with customized versions, and adding methods of your own. None of this is available with respect to COM objects. Although it is possible to inherit an interface, that does not mean inheritance of functionality; the interface contains no implementation. Instead, COM objects are treated as black boxes. Nothing is known about the details of the implementation of the interface, only the specifications of the interface itself. In other words, what we know is the object's behavior, not how that behavior is implemented. OLE offers two specific mechanisms for reusability. Containment/delegation is a mechanism whereas "outer" objects act as clients to "inner" objects acting as servers. Sounds familiar? Think of an OLE drawing embedded in a Word document. The other mechanism, aggregation, enables outer objects to expose interfaces from inner objects, making it appear as if the outer object implemented those interfaces itself. Interfaces are identified through globally unique identifiers, or GUIDs. GUIDs are 128-bit identifiers that are unique (hopefully) throughout the world. 14.10.3 OLE Class Objects and Registration

Figure 14.9(g) Running instance of the client invoking the COM server This example demonstrated the basic concepts of creating a COM server and requesting its services from a client. Although you created the empty project by using the appwizard, the core functionality of the server such as creating the interfaces and registering the server was done manually.

14.10 Object Linking and Embedding (OLE) Object linking and embedding is at the core of most modern Windows applications. It is also a very complex technology that would be difficult to master without the help of MFC. However, in order to use the MFC Library efficiently for OLE applications, it is helpful to have a solid understanding of OLE fundamentals. While not strictly needed if you are satisfied with the "stock" implementation of basic OLE features in your application (provided by AppWizard), this understanding becomes essential if An OLE class object should not be confused with the concept of a class in object-oriented languages. It is merely another COM object, one that specifically implements the IClassFactory interface. This interface is the key to a fundamental OLE capability. Through the IClassFactory interface, applications that were written without any knowledge as to the particular class can still create objects for that class. This is accomplished in part by registering the class, and in part by specific methods within the class.A class is identified through a CLSID, which is just another GUID. The operating system maintains

a database of all registered CLSIDs in the system. What this means in the Windows environment is a set of entries in the Windows Registry. Registration entries are made in the form of a subkey under HKEY_CLASSES_ROOT\CLSID, identified by the CLSID in string form. Applications that are aware of a CLSID can use the OLE API functions CoGetClassObject and CoCreateInstance to create a class object, or create an uninitialized object of the kind associated with the CLSID. 14.10.4 OLE and Threads OLE defines a specific approach for thread-safe implementation. This apartment model defines a set of rules applications must follow if they wish to create and access objects from within separate threads of the same process. The apartment model groups objects by owner thread. Objects can only live in a single thread (an apartment). Within the same thread, methods can be called directly; however, when calls are made across thread boundaries, the same marshaling technique must be used as with calls across process boundaries. The OLE libraries provide a set of helper functions for this purpose. 14.10.5 OLE and Compound Documents The most commonly known use of OLE technology is in the form of OLE containers and servers. Together, container and server applications enable users to manipulate, within a single application, data coming from different sources and several applications. Compound document technology is based, in addition to the Component Object Model, on OLE Structured Storage and OLE Uniform Data Transfer. 14.10.6 Structured Storage OLE provides two interfaces that closely mimic traditional functions found in most disk-based file systems. The IStorage interface provides functionality analogous to that of file systems (directories). Just like disk-based directories, a storage object can contain hierarchical references to other storage objects. It also tracks the locations and sizes of other objects stored within. The IStream interface is analogous to a file. As its name implies, a stream object contains data as a contiguous sequence of bytes. OLE compound files consist of a root storage object with at least one stream object representing native data. Additional storage objects can represent linked or embedded items. File-based storage is implemented with the help of the IRootStorage interface. Objects that can be embedded within container application documents must implement the IPersistStorage interface, which enables the object to be saved in a storage object. Other persistent storage-related interfaces include IPersistStream and IPersistFile. Structured storage has many benefits other than providing the means to treat a hierarchical set of objects as a single file. As in the case of real file systems, replacing a single object does not require that the entire compound file be rewritten. Objects can be accessed individually, without having to load the entire file. Structured storage

also provides facilities for concurrent access by several processes and for transaction-based processing (commit and reverse functionality). The OLE compound file implementation is operating system and file system independent. A compound file created, for example, on a FAT file system under Windows 95 can be reused from within a Windows NT application on an NTFS file system or by a Macintosh application using the Macintosh file system. Storage and stream objects are named according to a set of conventions. The root storage object is named as the underlying file, with the same restrictions on naming as applicable for the file system. Names of nested elements that are up to 32 characters in length (including any terminating null characters) must be supported by implementations. Whether a case conversion is applied to names or not is an implementation-defined behavior. 14.10.7 Data Transfer Transferring data between applications is accomplished through the IDataObject interface. This interface provides a mechanism for transferring data and also for notifications of changes in the data. Data transfer occurs with the help of two structures: FORMATETC and STGMEDIUM. The FORMATETC structure is defined as follows: typedef struct tagFORMATETC { CLIPFORMAT cfFormat; DVTARGETDEVICE *ptd; DWORD dwAspect; LONG lindex; DWORD tymed; }FORMATETC; *LPFORMATETC; This structure generalizes the idea of clipboard formats, providing, in addition to the cfFormat parameter, parameters that identify the target device for which the data was composed and information on how the data should be rendered. The STGMEDIUM structure generalizes the idea of global memory handles used in traditional Windows clipboard operations. This structure is defined as follows: typedef struct tagSTGMEDIUM { DWORD tymed; union { HBITMAP hBitmap; HMETAFILEPICT hMetafilePict; HENHMETAFILE hEnhMetaFile; HGLOBAL hGlobal; LPOLESTR lpszFileName; IStream *pstm; IStorage *pstg; }; IUnknown *pUnkForRelease; }STGMEDIUM; Through these two structures, providers and recipients of data can negotiate the types of data they can send and accept along with the most efficient storage mechanism used in transmitting the data. 14.10.8 Compound Documents

OLE compound documents may, in addition to native data, contain two types of items: linked objects and embedded objects. Linked objects represent objects that continue to reside where they were originally created (for example, in a file). The compound document contains a reference to this item (a link) and information on how the item should be presented. The container can present the linked item without activating the linkindeed, it can present the item even if the application that created the item is not available on a particular system. Activating the link means invoking the server application for editing and manipulating link data. The use of links results in small container documents and is also beneficial if the linked item is routinely maintained by a user other than the owner of the container document. Embedded objects reside physically within the container document. The advantage of using embedded objects is that documents can be manipulated as single files; in contrast, when linked items are used, several files may need to be exchanged between users. Furthermore, links are easily broken if linked items are moved. (Windows currently does not implement a tracking mechanism for linked items.) Servers for objects in a container document are implemented either as in-process servers or as local servers. An in-process server is essentially a DLL running in the process space of the container application. The major advantage of an in-process server is performance; methods in such a server are called directly, without the associated OLE overhead. However, local servers offer several benefits also. They can support links (in-process servers cannot); they provide compatibility with OLE1; and they provide the added benefits of running in a separate process space (increased robustness, ability to serve multiple concurrent clients). Compound documents also support in-place activation. The in-place activation mechanism enables embedded items to be edited within the container application's window. Basic support for compound document containers and servers comes in the form of the IOleClientSite and IOleObject interfaces. Servers also implement the IDataObject and IPersistStorage interface; furthermore, in-process servers implement IViewObject2 and IOleCache2. In-place activation is accomplished via the IOleInPlaceSite, IOleInPlaceObject, IOleInPlaceFrame, and IOleInPlaceActiveObject interfaces. 14.11 Applications of OLE Calling OLE Object Linking and Embedding is somewhat of a misnomer. As the discussion to this point should have made obvious, OLE is a set of specifications going far beyond mere object linking and embedding capability. Indeed, compound documents are just one of the many applications of OLE; other uses include OLE automation, OLE drag and drop, and OLE controls, which we review shortly. OLE also finds its way into specialized areas; for example, much of MAPI, the Messaging Application Programming Interface, is based on OLE. Most OLE applications require specific registration entries. Entries in the Windows Registry are typically made under the keys HKEY_CLASSES_ROOT\CLSID and HKEY_CLASSES_ROOT\Interface. 14.11.1 OLE Document Containers and Servers OLE containers and servers together implement compound document technology. OLE containers maintain compound documents consisting of linked and embedded items; OLE servers provide such

linked and embedded items and the functionality required for their activation. 14.11.2 OLE Automation OLE automation enables an OLE automation server application to expose automation objects through a series of properties and methods. Information about properties and methods is published through the IDispatch interface. By querying this interface, automation clients can obtain information about the properties and methods identified by name. OLE automation objects need not be visible objects. For example, an OLE automation server may perform scientific calculations, do spell checking, or supply physical constants identified by name, without ever presenting a visual interface on its own. OLE automation clients, also known as automation controllers, are applications that manipulate OLE automation objects. Automation controllers can be generic (for instance a programming environment, such as Visual Basic) or can be developed to control specific automation objects. 14.11.3 OLE Drag and Drop OLE drag and drop provides a powerful mechanism for implementing drag and drop functionality. OLE drag and drop capabilities are implemented through the IDropSource and IDropTarget interfaces and the DoDragDrop function. After receiving the data item that is the object of the drag and drop operation and a pointer to an IDropSource interface, DoDragDrop enters a loop during which it tracks mouse events. If the mouse is over a window, DoDragDrop checks whether that window registered as a valid drop target. DoDragDrop calls various methods of IDropTarget and IDropSource to carry out its operation. Drag and drop functionality and clipboard cut and paste are very similar. Often it is beneficial to implement these two areas of functionality together, reusing code as much as possible. 14.11.4 OLE Controls OLE controls represent a 32-bit replacement technology for Visual Basic controls. OLE controls are OLE objects that provide an extended interface, which implements the behavior of a Windows control. OLE control servers are typically implemented as in-process servers (an OCX file is simply a dynamic link library with a special filename extension). OLE control containers are applications that support OLE controls in their windows or dialogs. 14.11.5 OLE Custom Interfaces For specialized applications, you can also develop your own OLE custom interfaces. Custom interfaces are developed using the tools available in the Microsoft Windows Software Development Kit, including the Microsoft Interface Definition Language (MIDL) compiler.

You might also like