COM Tutorial – page 2

 

10.Other

ATL Supporting Classes

10.1.    

 

CComObject<> Template Classes – Creatable

Coclasses

The coclass you create with COM AppWizard does not contain

the implementation of IUnknown methods. Therefore they are abstract

classes. They can not be directly created. Instead a coclass is passed to one

of a set of template classes, which inherits from the coclass passed as

template parameter, and provides a specific implementation of the IUnknown

methods. This template classes become the real creatable coclasses.

The reason behind these logic is to separate the custom

implementation of coclasses from the implementation of IUnknown, so that the

ATL coclasses are more portable.

The definition of the most commonly used template class CComObject<>

is listed below:

 

template <class Base>

class CComObject

: public Base

{

public:

typedef Base _BaseClass;

 

CComObject(void* = NULL)

{

_Module.Lock(); // _Module is the global CComModule

}

~CComObject() // Set refcount to 1 to protect destruction

{

m_dwRef = 1L;

FinalRelease();

_Module.DeleteNonAddRefThunk(_GetRawUnknown());

_Module.Unlock();

}

 

STDMETHOD_(ULONG, AddRef)()

{

return InternalAddRef();

}

 

STDMETHOD_(ULONG, Release)()

{

ULONG l = InternalRelease();

if (l == 0)

delete this;

return l;

}

 

STDMETHOD(QueryInterface)(REFIID

iid, void ** ppvObject)

{

return _InternalQueryInterface(iid,

ppvObject);

}

 

template <class Q>

HRESULT

STDMETHODCALLTYPE QueryInterface(Q** pp)

{

return QueryInterface(__uuidof(Q), (void**)pp);

}

 

static HRESULT WINAPI CreateInstance(CComObject<Base>**

pp);

};

 

template <class Base>

HRESULT WINAPI CComObject<Base>::CreateInstance(CComObject<Base>** pp)

{

ATLASSERT(pp != NULL);

HRESULT hRes = E_OUTOFMEMORY;

CComObject<Base>* p = NULL;

ATLTRY(p = new CComObject<Base>())

if

(p != NULL)

{

p->SetVoid(NULL);

p->InternalFinalConstructAddRef();

hRes = p->FinalConstruct();

p->InternalFinalConstructRelease();

if (hRes != S_OK)

{

delete p;

p = NULL;

}

}

*pp

= p;

return hRes;

}

10.2.    

 

Create a COM Object Internally

ATL’s COM map macros make use of those CComObject<>

templates to create real coclass instances, so you normally do not need to

directly deal with them. However, if you need to create an instance of an ATL coclass

which is defined within your project (in such case you do not need to go

through COM run time by calling CoCreateInstance), you need to use one

of these template classes manually:

 

CComObject<CAnyCoclass> * pAny = NULL;

CComObject<CAnyCoclass>::CreateInstance(&pAny);

 

The template classes are:

1.        

CComObject<>non-aggregated, heap-based, affects the server’s

object count;

2.        

CComObjectNoLock<>non-aggregated, does not adjust the server’s object count;

3.        

CComAggObject<>can work as aggregate;

4.        

CComObjectStack<>stack-based;

5.        

CComObjectGlobal<>life time depends on the life time of the server;

6.        

CComPolyObject<>may or may not work as aggregate;

7.        

CComTearOffObject<>implements a tear-off interface.

10.3.    

 

CComObjectRootBase

The hierarchy of an ATL coclass looks like:

 

class ATL_NO_VTABLE

CAny :

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<CAny,

&CLSID_Any>,

public IDispatchImpl<IAny, &IID_IAny, &LIBID_TESTLib>

{

As we have discussed before, template class CComObjectRootEx

is responsible for providing threading support Increment, Decrement,

Lock and Unlock. It inherits from class CComObjectRootBase,

which provides the following functionality:

¨    FinalConstruct

and FinalRelease

In <atldef.h>, there are following lines:

 

#ifdef _ATL_DISABLE_NO_VTABLE

#define ATL_NO_VTABLE

#else

#define ATL_NO_VTABLE __declspec(novtable)

#endif

If you do define _ATL_DISABLE_NO_VTABLE, macro _ATL_NO_VTABLE

will extend to nothing. Otherwise it will extend to declarator __declspec(novtable),

which stops the compiler from generating code to initialize the vPtr in

the constructor(s) and destructor of the class. In many cases, this removes the

only references to the vtable that are associated with the class and, thus, the

linker will remove it. This can result in a significant reduction in code size.

Only the most derived class needs vTable. ATL coclasses are not most derived

classes, so they don’t need a vTable.

After the constructor call, you will have a vTable to use.

However, without a vtable, you can not call any virtual

methods in your constructor. That’s why CComObjectRootBase provides

method FinalConstruct and FinalRelease, in which you can call

virtual methods and do initialization and clean up. The framework will call

them after the instance is constructed or before it is destroyed.

¨    InternalQueryInterface

CComObjectRootBase maintains a reference count m_dwRef

for the coclass. It also provides method InternalQueryInterface, which

turns around and calls AtlInternalQueryInterface, which iterates over

the COM map and searches for an interface.

The abstract ATL coclass indirectly inherits from CComObjectRootBase,

and is passed into and inherited by the template classes such as CComObject<>.

The template class can then call InternalQueryInterface in its

implementation of IUnknown::QueryInterface. InternalAddRef and InternalRelease

are provided by the CComObjectRootEx<> itself.

¨    Support

for aggregation

CComObjectRootBase provides method OuterAddRef,

OuterRelease and OuterQueryInterface and a public IUnknown

pointer m_pOuterUnknown.

¨    COM

map macro helper methods

CComObjectRootBase provides a set of internal helper

functions so that COM map macros can call to calculate vPtr for a given

interface, including _Chain, _Break, _NoInterface, _Cache,

_Delegate and _Creator.

10.4.    

 

CComCoClass<>

The other template class which the ATL coclass inherits from

is CComCoClass<>. It provides mainly three services:

1.       It

contains a call to macro DECLARE_CLASSFACTORY, which extends to typedef CComClassFactory

to by your default class factory.

2.       It

invokes macro DECLARE_AGGREGATABLE to instruct ATL framework how the

colcass should function – as a stand-alone or aggregated object.

3.       It

provides a number of overloaded Error methods to return error information

to the client.

11.ATL’s

COM Map

11.1.    

 

vPtrs of Interfaces

As you can see from chapter “Polymorphism” section “Late Binding with vtable and vPtr” in my C++ tutorial, because interfaces do not

have data members, in a COM component’s memory footprint, the vPtrs of

all interfaces that this component implements are placed one next to another.

Each vPtr points to a vtable of one interface, whose function

pointers points to the latest/lowest implementations of the interface’s

methods. Returning a reference of an interface to the client is all about

returning the vPtr of the interface to the client.

11.2.    

 

COM Map

An ATL COM map provides a static array of _ATL_INTMAP_ENTRY

structures, which stores the IIDs and vPtrs of all the interfaces

implemented by the coclass. It also provides some simple functions that can be

called to acquire from the array the vPtr of an interface given its IID.

An ATL COM map in a coclass definition looks like

 

class ATL_NO_VTABLE CSource :

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<CSource,

&CLSID_Source>,

public IDispatchImpl<ISource, &IID_ISource,

&LIBID_THREADSLib>

{

public:

CSource()

{}

 

DECLARE_REGISTRY_RESOURCEID(IDR_SOURCE)

DECLARE_PROTECT_FINAL_CONSTRUCT()

 

BEGIN_COM_MAP(CSource)

COM_INTERFACE_ENTRY(ISource)

COM_INTERFACE_ENTRY(IDispatch)

END_COM_MAP()

 

// ISource

public:

STDMETHOD(Advise)(/*[in]*/ ICallBack * pCallBack);

STDMETHOD(DoWork)();

 

private:

ICallBack * m_pCallBack;

};

The above four macros will expend to the following lines:

 

//****************** BEGIN_COM_MAP macro *****************

public:

typedef CSource

_ComMapClass;

 

static HRESULT WINAPI _Cache

(void* pv, REFIID iid, void** ppvObject, DWORD dw)

{

_ComMapClass* p = (_ComMapClass*)pv;

p->Lock();

HRESULT hRes = CComObjectRootBase::_Cache(pv, iid, ppvObject, dw);

p->Unlock();

return hRes;

}

 

IUnknown* _GetRawUnknown()

{

ATLASSERT(_GetEntries()[0].pFunc

== _ATL_SIMPLEMAPENTRY);

return (IUnknown*)((int)this + _GetEntries()->dw);

}

 

IUnknown* GetUnknown()

{

return _GetRawUnknown();

}

 

HRESULT _InternalQueryInterface(REFIID

iid, void** ppvObject)

{

return InternalQueryInterface(this,

_GetEntries(), iid, ppvObject);

}

 

const static _ATL_INTMAP_ENTRY * WINAPI _GetEntries()

 

{

static const _ATL_INTMAP_ENTRY

_entries[] =

{

//*********** COM_INTERFACE_ENTRY(ISource)

*************

{

// _ComMapClass is defined to

be CSource

&__uuidof(ISource),

offsetofclass(ISource,

_ComMapClass),

_ATL_SIMPLEMAPENTRY

},

 

//*********** COM_INTERFACE_ENTRY(IDispatch)

*************

{

&__uuidof(IDispatch),

offsetofclass(IDispatch,

_ComMapClass),

_ATL_SIMPLEMAPENTRY

},

 

//*********** END_COM_MAP macro

*************

{

NULL, 0,

0

}

};

 

return _entries;

}

11.3.    

 

_ATL_INTMAP_ENTRY structure

The structure is defined as follow:

 

struct _ATL_INTMAP_ENTRY

{

const

IID * piid;// the interface id (IID)

DWORD dw;

_ATL_CREATORARGFUNC

* pFunc; //NULL:end, 1:offset, n:ptr

};

piid is the IID of the interface, which is calculated

in the macro by keyword __uuidof,

which can retrieve the uuid attached to a type definition.

dw is the offset to the vPtr of a given

interface in relate to the coclass’s this pointer, which is calculated

in the macro by another macro offsetofclass.

pFunc can either be set to _ATL_SIMPLECOMENTRY

or points to one of the CComObjectRootBase helper functions such as _Creator

or _Cache. If pFunc is _ATL_SIMPLECOMENTRY, it tells AtlInternalQueryInterface

that this interface is an “internal” interface directly inherited by

the cocloass, not an aggregated or tear-off interface. Therefore its address can

be calculated by the coclass’s this pointer plus the provided offset dw:

 

if (pEntries->pFunc ==

_ATL_SIMPLEMAPENTRY) //offset

{

IUnknown* pUnk = (IUnknown*)((int)pThis +

pEntries->dw);

If pFunc is not _ATL_SIMPLECOMENTRY, it tells AtlInternalQueryInterface

that the interface is an aggregation or tear-off interface. Than pFunc

is supposed to point to one of the CComObjectRootBase helper functions,

and AtlInternalQueryInterface calls that function through pFunc

to calculate the interface address:

 

HRESULT hRes = pEntries->pFunc(pThis, iid,

ppvObject, pEntries->dw);

11.4.    

 

vPtr of IUnknown interface

Because all interfaces inherit from IUnknown, the vtables

of all interface contains the function pointers of the three IUnknown

methods. All of these function pointers points to the same latest/lowest

implementations of the three methods. Therefore, you can access all three

methods of IUnknown with the vPtr of any interface.

When the client asks for the IUnknown interface of

the coclass, AtlInternalQueryInterface can simply get from the array the

vPtr of any internal interface and return it to the client. So it

chooses the first one in the map. All AtlInternalQueryInterface needs to

do is to make sure that this interface is an internal interface, not an

aggregated or tear-off interface, by checking that pEntries->pFunc is

equal to _ATL_SIMPLECOMENTRY. From the implementation of AtlInternalQueryInterface

you can see, the very first thing it does is to assert the above condition, and

if it fails, the whole method fails.

For this reason, you must make sure that the first entry in

th COM map is represented by one of the four macros, which are all used to

declare internal interfaces and all specify pFunc to be _ATL_SIMPLECOMENTRY:

1.       COM_INTERFACE_ENTRY

2.       COM_INTERFACE_ENTRY2

3.       COM_INTERFACE_ENTRY_IID

4.       COM_INTERFACE_ENTRY2_IID

11.5.    

 

_InternalQueryInterface

An ATL coclass does not provide implementation of IUnknown

interface. Instead, a CComObject<> template classe takes the

coclass as template parameter and inherits from it, and provides the three

IUnknown methods. These methods simply turn around and call the InternalAddRef,

InternalRelease and _InternalQueryInterface.

InternalAddRef and InternalRelease are

provided by CComObjectRootEx<>, which turn around and call the Increment

and Decrement method provided by the threading model class passed as

template parameter.

_InternalQueryInterface is provided by the

above-shown COM map macro, which turns around and calls InternalQueryInterface

provided by CComObjectRootBase, which again turns around and calls ATL

API AtlInternalQueryInterface, which makes use of the COM map and

searches for an interface with an IID:

 

ATLINLINE ATLAPI AtlInternalQueryInterface(

void *pThis,

// “this” pointer of the coclass

const _ATL_INTMAP_ENTRY *pEntries,

// The array

REFIIDiid,

// Interface IID

void **ppvObject) // Receiving interface pointer

{

ATLASSERT(pThis !=

NULL);

 

//

First entry in the com map should be a simple map entry

ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);

 

if

(ppvObject == NULL) // No receiving pointer is passed

return E_POINTER;

 

*ppvObject = NULL;

if

(InlineIsEqualUnknown(iid)) // use the first

interface

{

IUnknown * pUnk = (IUnknown *)((int)pThis +

pEntries->dw);

pUnk->AddRef();

*ppvObject = pUnk;

return S_OK;

}

 

while (pEntries->pFunc != NULL)

{

BOOL bBlind =

(pEntries->piid == NULL);

 

if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid))

{

if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY)// offset

{

ATLASSERT(!bBlind);

IUnknown* pUnk = (IUnknown*)((int)pThis + pEntries->dw);

pUnk->AddRef();

*ppvObject = pUnk;

return S_OK;

}

else//actual function call

{

HRESULT hRes =

pEntries->pFunc(pThis,

iid, ppvObject, pEntries->dw);

if (hRes == S_OK || (!bBlind

&& FAILED(hRes)))

return hRes;

}

}

 

pEntries++;

}

return E_NOINTERFACE;

}

11.6.    

 

Interface Method Name Clash

Suppose we have two interfaces IFirst and ISecond,

each with a method Hi. As you can see from section “Method Name Clash and Ambiguity in Multiple Inheritance”

in chapter “Multiple Inheritance” in my C++ tutorial, if we want to have to

different implementations of method Hi, we must have two wrapper classes

implementing the two interfaces, and let the coclass inherit from these two

classes.

The two wrapper classes only need to implement the clashing

method Hi (therefore they are still abstract classes), leaving

non-clashing methods (MethodFirst and MethodSecond) to be

implemented by the coclass as usual:

 

// **************** Test.idl

******************

interface IFirst

: IUnknown

{

[helpstring("method

Hi")] HRESULT Hi();

[helpstring("method

MethodFirst")] HRESULT MethodFirst();

};

interface ISecond

: IUnknown

{

[helpstring("method

Hi")] HRESULT Hi();

[helpstring("method

MethodSecond")] HRESULT MethodSecond();

};

 

// ************* Impls.h *******************

struct IFirstImpl

: public IFirst {

STDMETHODIMP

Hi()

{

m(“Hi

from IFirstImpl!”);

return

S_OK;

}

};

 

struct ISecondImpl

: public ISecond {

STDMETHODIMP

Hi()

{

m(“Hi

from ISecondImpl!”);

return

S_OK;

}

};

 

 

// ************ CAny.h ***************

#ifndef __ANY_H_

#define __ANY_H_

 

#include “resource.h”// main symbols

#include “impls.h”

 

class ATL_NO_VTABLE CAny :

public

CComObjectRootEx<CComSingleThreadModel>,

public

CComCoClass<CAny, &CLSID_Any>,

public

IFirstImpl,

public

ISecondImpl

{

public:

CAny()

{}

 

DECLARE_REGISTRY_RESOURCEID(IDR_ANY)

DECLARE_NOT_AGGREGATABLE(CAny)

 

DECLARE_PROTECT_FINAL_CONSTRUCT()

 

BEGIN_COM_MAP(CAny)

COM_INTERFACE_ENTRY(IFirst)

COM_INTERFACE_ENTRY(ISecond)

END_COM_MAP()

 

public:

STDMETHOD(MethodSecond)();

STDMETHOD(MethodFirst)();

//STDMETHOD(Hi)();// Do not implement!

};

 

#endif //__ANY_H_

The COM map still gives the client the vPtrs of the

two interfaces, not that of the two wrapper classes. Actually you can put the

name of the wrapper classes instead of the interfaces in the COM map, so that

it gives the client the vPtrs of the wrapper classes. It doesn’t matter,

because the function pointers of method Hi in the vtables in both

the interface and its wrapper class point to the implementation in the wrapper

class. But to use the second option you have to give the IID of the interfacess

to their wrapper classes.

11.7.    

 

Solving Inheritance Path Ambiguity with COM Map

Macros

Suppose we have two interfaces IAquatic and IMammal

which inherit from interface ICreature. Coclass CDelphin implements

all three interfaces: ICreature, IAquatic and IMammal.

This coclass in ATL will look like

 

class ATL_NO_VTABLE CDelphin :

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<CDelphin, &CLSID_Delphin>,

public IAquatic, // Instead of

IInterf1

public IMammal// Instead of IInterf2

{

public:

CDelphin()

{

}

 

DECLARE_REGISTRY_RESOURCEID(IDR_DELPHIN)

DECLARE_NOT_AGGREGATABLE(CDelphin)

 

DECLARE_PROTECT_FINAL_CONSTRUCT()

 

BEGIN_COM_MAP(CDelphin)

COM_INTERFACE_ENTRY(ICreature) //

Ambiguious!

COM_INTERFACE_ENTRY(IAquatic)

COM_INTERFACE_ENTRY(IMammal)

END_COM_MAP()

};

When compiler casts the coclass’s this pointer to ICreature,

it sees two paths, so it will be confused. We have to let the compiler know

which path to take. Now suppose we want to take IMammal’s path, we can

use one of the following three macros to substitute the basic COM_INTERFACE_ENTRY:

 

COM_INTERFACE_ENTRY2(ICreature, IMammal)

// or

COM_INTERFACE_ENTRY_IID(IID_ICreature, IMammal) // or

COM_INTERFACE_ENTRY2_IID(IID_ICreature, ICreature, IMammal)

11.8.    

 

Tear-off Interface

Because of the nature of COM – once a coclass supports an

interface it must forever support it, with the evolution of the server, there

must be some interfaces which are old and have very little chance to be used.

If a coclass implements lots of such old interfaces, it will carry some

performance and memory overhead – it takes time and memory to create the vPtrs

of the old interfaces.

For each of these old interfaces, we can create a wrapper

class to implement it separately from the coclass. The coclass no longer

implement the old interface. In its QueryInterface, when this old

interface is asked, we simply create a new instance of the wrapper class, cast

it and return it to the client. Such an “stand-alone” interface is called

tear-off interface. The coclass is called owner class.

The wrapper class has the same reference count m_refCount

and the same implementation of AddRef and Release as the coclass,

so that it can delete itself when not referenced anymore. It holds a pointer to

the owner class, so that in its own QueryInterface it can call the

owner’s QueryInterface to direct the queries back to the owner.

11.9.    

 

Tear-off interface in raw C++

 

// ****************** Base.h

*********************

#include “unknwn.h”

#include <windows.h>

 

struct IPopular

: public IUnknown {

STDMETHOD(Hi)() PURE;

};

 

struct IOld

: public IUnknown {

STDMETHOD(Hello)() PURE;

};

 

// ******************* Old.h **********************

#include “stdafx.h”

#include “base.h”

#include “owner.h”

 

class COld

: public IOld {

private:

COwner * m_pOwner;

ULONG m_refCount;

 

public:

COld(COwner * pOwner);

STDMETHODIMP_(ULONG) AddRef();

STDMETHODIMP_(ULONG)

Release();

STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);

STDMETHODIMP Hello();

};

 

//******************* Old.cpp

**********************

#include “stdafx.h”

#include “Old.h”

#include “iid.h”

 

COld::COld(COwner * pOwner) : m_refCount(0), m_pOwner(pOwner) {}

 

STDMETHODIMP_(ULONG) COld::AddRef()

{

return ++m_refCount;

}

 

STDMETHODIMP_(ULONG) COld::Release()

{

if(–m_refCount == 0)

{

delete this;

return 0;

}

else

return m_refCount;

}

 

STDMETHODIMP COld::QueryInterface(REFIID

riid, void ** ppv)

{

if(riid == IID_IOld)

*ppv = (IOld *)this;

else

return m_pOwner->QueryInterface(riid, ppv);

return S_OK;

}

 

STDMETHODIMP COld::Hello()

{

printf(“Hello

from COld!\n”);

return S_OK;

}

 

//**************** Owner.h ******************

#include “stdafx.h”

#include “base.h”

 

class COwner

: public IPopular {

public:

COwner();

STDMETHODIMP_(ULONG) AddRef();

STDMETHODIMP_(ULONG) Release();

STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);

STDMETHODIMP Hi();

 

private:

ULONG m_refCount;

};

 

//****************** Owner.cpp

*****************

#include “stdafx.h”

#include “owner.h”

#include “Old.h”

#include “iid.h”

 

COwner::COwner() : m_refCount(0)

{}

 

STDMETHODIMP_(ULONG) COwner::AddRef()

{

return ++m_refCount;

}

 

STDMETHODIMP_(ULONG) COwner::Release()

{

if(–m_refCount == 0)

{

delete this;

return 0;

}

else

return m_refCount;

}

 

STDMETHODIMP COwner::QueryInterface(REFIID

riid, void ** ppv)

{

if(riid == IID_IUnknown)

*ppv = (IUnknown *)this;

else if(riid == IID_IPopular)

*ppv = (IPopular *)this;

else if(riid == IID_IOld)

*ppv = (IOld *)new COld(this);

else

{

*ppv = NULL;

return E_NOINTERFACE;

}

return S_OK;

}

 

STDMETHODIMP COwner::Hi()

{

printf(“Hi from COwner!\n”);

return S_OK;

}

 

//******************* main

*********************

int main(int argc, char* argv[])

{

COwner * pOwner =

new COwner;

 

IPopular * pPopular;

HRESULT hr = pOwner->QueryInterface(IID_IPopular, (void

**)&pPopular);

pPopular->Hi();

 

//

Querying the old interface from the polular one

IOld * pOld;

hr

= pPopular->QueryInterface(IID_IOld,

(void **)&pOld);

pOld->Hello();

 

//

Querying the popular interface from the old one

hr

= pOld->QueryInterface(IID_IPopular,

(void **)&pPopular);

pPopular->Hi();

 

return 0;

}

Output will be:

 

Hi from COwner!

Hello from COld!

Hi from COwner!

11.10.Tear-off

interface with COM map macros

¨    CComTearOffObjectBase

In ATL, class CComTearOffObjectBase is provided to be

used as a base class of a tear-off class. It inherits from CComObjectRootEx

to provide synchronization support to the tear-off class, and added the needed

pointer back to the owner class:

 

template <class Owner, class ThreadModel = CComObjectThreadModel>

class CComTearOffObjectBase

: public CComObjectRootEx<ThreadModel>

{

public:

typedef

Owner _OwnerClass;

CComObject<Owner>* m_pOwner;

CComTearOffObjectBase()

{m_pOwner = NULL;}

};

¨    ATL

tear-off class

An ATL tear-off class inherits from CComTearOffObjectBase

and implement the tear-off interface. It looks quite like a normal coclass

except that it does not inherit from CComCoClass, which provides class

factory and aggregation support:

 

class COwner;

 

class ATL_NO_VTABLE COld :

public CComTearOffObjectBase<COwner, CComSingleThreadModel>,

public IOld

{

public:

STDMETHOD(Hi)();

 

BEGIN_COM_MAP(COld)

COM_INTERFACE_ENTRY(IOld)

END_COM_MAP()

};

¨    ATL

owner class

An ATL owner class looks like:

 

class ATL_NO_VTABLE COwner :

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<COwner, &CLSID_Popular>,

public IPopular

{

public:

COwner()

{

}

 

DECLARE_REGISTRY_RESOURCEID(IDR_POPULAR)

DECLARE_NOT_AGGREGATABLE(COwner)

 

DECLARE_PROTECT_FINAL_CONSTRUCT()

 

BEGIN_COM_MAP(COwner)

COM_INTERFACE_ENTRY(IPopular)

COM_INTERFACE_ENTRY_TEAR_OFF(IID_IOld, COld)

END_COM_MAP()

 

public:

friend class COld;

}

It does not inherit from the tear-off interface, and use COM

map macro COM_INTERFACE_ENTRY_TEAR_OFF to configure its QueryInterface.

11.11.Caching

the Tear-off Class

There is a slight problem with the above

implementation both in raw C++ and in ATL: every time the client queries for

the tear-off interface, a new instance of the tear-off class is created. To

cache the tear-off class, make the following change on the owner class

(tear-off class does not need any change):

1.       Add

macro DECLARE_GET_CONTROLLING_UNKNOWN in the owner class definition,

which expends to method GetControllingUnknown which can be called by

others to get the IUnknown pointer of the owner class:

2.       Add

a public IUnknown member to the owner class, initialize it to NULL in

the constructor;

3.       Substitute

the COM_INTERFACE_ENTRY_TEAR_OFF macro with COM_INTERFACE_ENTRY_CACHED_TEAR_OFF,

and pass in the IUnknown member as the third parameter.

4.       In

the FinalRelease method, release the IUnknown pointer.

The owner class will look like:

 

class ATL_NO_VTABLE COwner :

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<COwner, &CLSID_Popular>,

public IPopular

{

public:

COwner() : m_pUnk(NULL)

{}

 

void FinalRelease()

{

if(m_pUnk)

m_pUnk->Release();

}

 

DECLARE_GET_CONTROLLING_UNKNOWN()

DECLARE_REGISTRY_RESOURCEID(IDR_POPULAR)

DECLARE_NOT_AGGREGATABLE(COwner)

 

DECLARE_PROTECT_FINAL_CONSTRUCT()

 

BEGIN_COM_MAP(COwner)

COM_INTERFACE_ENTRY(IPopular)

COM_INTERFACE_ENTRY_CHCHED_TEAR_OFF(IID_IOld, COld, m_pUnk)

END_COM_MAP()

 

public:

friend class COld;

IUnknown * m_pUnk;

}

11.12.COM

Component Containment

When you are designing a COM component, if you think some

work can be done by another COM component so that you can avoid rewriting the

code, you can start to use COM component containment. No new techniques is involved.

Just invoke that component using basic COM API function calls such as CoCreateInstance

and QueryInterface. Nothing needs to be done on the COM map.

A common way to do that is to have some interface pointers

of the contained component as your data members, call CoCreateInstance

and QueryInterface to initialize them in your FinalConstruct, and

release them in your FinalRelease.

11.13.Making

a Coclass Aggregable

In aggregation, the outer component exposes the

inner-component interfaces directly to clients, so that from the client’s point

of view these inner-component interfaces are fully supported by the outer

component. This process is quite complicated and better be done with ATL

support.

To be able to server as inner object, a COM coclass

must conform to the following conditions:

1.       It

must reside in a DLL not an EXE server.

2.       If

you want the coclass to be able to be aggregated by an EXE server, it must

register its proxy/stub DLL server.

3.       The

outer object and the inner object must be in the same threading apartment.

4.       When

you create the coclass using ATL object wizard, you must NOT tick “No

as the aggregation option.

Option “No” will insert

into your coclass definition macro DECLARE_NOT_AGGREGATABLE, which

disables the coclass to be aggregated. Option “Yes” will insert DECLARE_AGGREGATABLE,

which enables the coclass to server as either a stand-alone object or an inner

object. Option “Only” will insert macro DECLARE_ONLY_AGGREGATABLE,

which restrains the coclass to only server as an inner object.

Macro DECLARE_AGGREGATABLE

is defined in CComCoClass. It will be discussed together with ATL’s

object map.

11.14.How

to Aggregate Other Aggregable Coclasses

There are three ways for a coclass to aggregate

other aggregable coclasses:

¨    Selective

Aggregation

Selective aggregation is achieved with COM map macro COM_INTERFACE_ENTRY_AGRREGATE.

The outer component selectively exposes some of the inner-component interfaces

to clients. Only when clients query for these selected interfaces will the

outer-component QueryInterface directly the queries to the inner

component.

To selectively aggregate an aggregable inner class, an outer

object must do the following things:

1.       Add

macro DECLARE_GET_CONTROLLING_UNKNOWN to the outer-class definition,

which defines method GetControllingUnknown, which acquires the IUnknown

pointer of the outer class;

2.       Have

a IUnknown pointer as data member;

3.       In

FinalConstruct, create an instance of the inner object by calling CoCreateInstance

passing the outer object’s IUnknown pointer as the second parameter, which

is acquired by method call GetControllingUnknown. Assign the acquired IUnknown

pointer to the member pointer.

4.       In

FinalRelease, release the inner-object IUnknown member;

5.       Declare

each inner-object interface which you would like to expose with macro COM_INTERFACE_ENTRY_AGGREGATE

in the COM map, passing the inner-object IUnknown pointer as the second

parameter.

6.       Add

the inner-object interfaces into the outer-class IDL class definition.

 

// ******************** AnyOuter.h

*********************

#include “resource.h”

#include

“Inner.h”

 

class ATL_NO_VTABLE CAnyOuter :

public CComObjectRootEx<CComMultiThreadModel>,

public CComCoClass<CAnyOuter, &CLSID_AnyOuter>,

public IAnyOuter// The outer

class’s own interface

{

public:

CAnyOuter() : m_pInnerUnk(NULL) {}

HRESULT FinalConstruct();

void FinalRelease();

 

DECLARE_GET_CONTROLLING_UNKNOWN()

DECLARE_REGISTRY_RESOURCEID(IDR_ANYOUTER)

DECLARE_PROTECT_FINAL_CONSTRUCT()

 

 

BEGIN_COM_MAP(CAnyOuter)

COM_INTERFACE_ENTRY(IAnyOuter)

COM_INTERFACE_ENTRY_AGGREGATE(IID_IInterf1, m_pInnerUnk)

COM_INTERFACE_ENTRY_AGGREGATE(IID_IInterf2, m_pInnerUnk)

END_COM_MAP()

 

public:

STDMETHOD(MethodOuter)();//

Outer class interface’s method

IUnknown * m_pInnerUnk;

};

 

// ***************** AnyOuter.cpp

*******************

#include “stdafx.h”

#include “Outer.h”

#include “AnyOuter.h”

#include

“Inner_i.c”

 

STDMETHODIMP CAnyOuter::MethodOuter()

{

::MessageBox(NULL, “MethodOuter!”, “Server Outer”,

MB_OK);

return S_OK;

}

 

HRESULT CAnyOuter::FinalConstruct()

{

HRESULT hr;

hr

= CoCreateInstance(

CLSID_Any,

GetControllingUnknown(),

CLSCTX_SERVER,

IID_IUnknown,

(void **)&m_pInnerUnk);

return hr;

}

 

void CAnyOuter::FinalRelease()

{

if(m_pInnerUnk)

m_pInnerUnk->Release();

}

¨    Blind

aggregation

In ATL’s blind aggregation, the outer object will direct

direct all queries for unsupported interfaces to the inner object. It is

achieved with COM map macro COM_INTERFACE_ENTRY_AGGREGATE_BLIND. The COM

map will become

 

BEGIN_COM_MAP(CAnyOuter)

COM_INTERFACE_ENTRY(IAnyOuter)

COM_INTERFACE_ENTRY_AGGREGATE_BLIND(m_pInnerUnk)

END_COM_MAP()

¨    Auto

aggregation

Auto aggregation is achieved with COM map macro COM_INTERFACE_ENTRY_AUTOAGGREGATE

and COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND. The inner object is not

created in the FinalConstruct, but by the COM macro, only when client

queries for the inner-object interface. This is quite like the purpose of

tear-off interface.

The above example is simplified and becomes:

 

#include “resource.h”

#include “Inner.h”

 

class ATL_NO_VTABLE CAnyOuter :

public CComObjectRootEx<CComMultiThreadModel>,

public CComCoClass<CAnyOuter, &CLSID_AnyOuter>,

public IAnyOuter

{

public:

CAnyOuter() : m_pInnerUnk(NULL) {}

void FinalRelease()

{

if(m_pInnerUnk)

m_pInnerUnk->Release();

}

 

DECLARE_GET_CONTROLLING_UNKNOWN()

DECLARE_REGISTRY_RESOURCEID(IDR_ANYOUTER)

DECLARE_PROTECT_FINAL_CONSTRUCT()

 

BEGIN_COM_MAP(CAnyOuter)

COM_INTERFACE_ENTRY(IAnyOuter)

//

Here m_pInnerUnk is used to receive the IUnknown pointer created

//

inside the macro

COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IInterf1,

m_pInnerUnk, CLSID_Any)

COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IInterf2,

m_pInnerUnk, CLSID_Any)

END_COM_MAP()

 

public:

STDMETHOD(MethodOuter)();

IUnknown * m_pInnerUnk;

};

Just like macro COM_INTERFACE_ENTRY_AGGREGATE_BLIND,

macro COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND is used to direct all

queries to the inner object.

11.15.Four

Last COM Map Macros

¨    COM_INTERFACE_ENTRY_BREAK

Using this macro in place of the standard macro COM_INTERFACE_ENTRY,

when the interface declared with this macro is queried, the macro will call

function DebugBreak to force the debugger to break.

¨    COM_INTERFACE_ENTRY_NOINTERFACE

This macro returns E_NOINTERFACE to explicitly tell

the world that this interface is not supported by this coclass.

¨    COM_INTERFACE_ENTRY_CHAIN

This macro takes a class name as parameter, and is used to “import”

the COM map of the base class as part of your own.

¨    COM_INTERFACE_ENTRY_FUNC

& COM_INTERFACE_ENTRY_FUNC_BLIND

COM_INTERFACE_ENTRY_FUNC takes three parameters: the

REFIID of the interface as usual, a general-purpose DWORD, and the name of a

global customized function which is written by you to replace the COM map

macro’s implementation of QueryInterface. The name of the function can

be anything, but the signature must be:

 

HRESULT WINAPI func(

void* pv,// “this” pointer of

the coclass

REFIID

riid,

LPVOID* ppv,// used to receive

the found interface pointer

DWORD dw);

When client queries for a REFIID, and this COM_INTERFACE_ENTRY_FUNC

macro is the first macro which specifies that REFIID, then this macro entry is entered,

and the macro will call that customized function. If the function finds the

interface, it will assign it to *ppv, and return S_OK. If it can not find the

interface, it should assign NULL to *ppv. If you want to continue searching the

rest of the COM map for following standard macros such as COM_INTERFACE_ENTRY

which might return the correct interface pointer, the customized function

should return S_FALSE. If you want to stop searching for that interface

and tell client that this coclass does not support this interface, the function

should return E_NOINTERFACE.

Therefore, if you want try your customized function first to

search for a particular interface, and if it fails, resort to the standard COM

map macro, then you should put the COM_INTERFACE_ENTRY_FUNC macro in

front of the standard one:

 

BEGIN_COM_MAP(CAnyOuter)

COM_INTERFACE_ENTRY(IAnyOuter1)

COM_INTERFACE_ENTRY_FUNC(IID_IAnyOuter2, 123, func)

COM_INTERFACE_ENTRY(IAnyOuter2)

COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IInterf1,

m_pInnerUnk, CLSID_Any)

COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IInterf2,

m_pInnerUnk, CLSID_Any)

END_COM_MAP()

If you put the standard macro in the front, the customized

function will never be called.

A good place to put this global customized function is the

source file of the coclass.

If you use macro COM_INTERFACE_ENTRY_FUNC_BLIND, then you do

not need to concern the location, for all interface queries will go to the

customized function first. If it returns S_FALSE, the rest of the COM map will

be searched. If it returns E_NOINTERFACE, the coclass will tell client that it

does not support the interface:

 

BEGIN_COM_MAP(CAnyOuter)

COM_INTERFACE_ENTRY(IAnyOuter1)

COM_INTERFACE_ENTRY(IAnyOuter2)

COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IInterf1,

m_pInnerUnk, CLSID_Any)

COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IInterf2,

m_pInnerUnk, CLSID_Any)

COM_INTERFACE_ENTRY_FUNC_BLIND(123, func)

END_COM_MAP()

12.ATL’s

Component Housing Support with Object Map

12.1.    

 

ATL’s Object Map and Component Housing

The functionality of a component housing (of either a DLL

server or an EXE server) is provided mainly by CComModule or its derived

class CExeModule. A CComModule or CExeModule knows nothing

about individual coclasses in the server. So an object map is provided to carry

specific information about each coclass, so that CComModule or CExeModule

can make use of. This is just like a COM map is designed to carry specific

information about each interface (their vPtrs), so that AtlInternalQueryInterface

can make use of.

12.2.    

 

CComModule in DLL Server

A DLL server has a global variable CComModule _Module

which provides the functionality of a component housing, including checking

object lock, creating class factories, and registering/unregistering the

coclasses. When one of the DLL’s exported functions is called, it simply

delegates the call to _Module:

 

CComModule _Module;

 

BEGIN_OBJECT_MAP(ObjectMap)

OBJECT_ENTRY(CLSID_Account,

CAccount)

END_OBJECT_MAP()

 

 

extern “C” BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason,

LPVOID)

{

if

(dwReason == DLL_PROCESS_ATTACH)

{

_Module.Init(ObjectMap, hInstance, &LIBID_BANKATLLib);

DisableThreadLibraryCalls(hInstance);

}

else if (dwReason == DLL_PROCESS_DETACH)

_Module.Term();

return TRUE;// ok

}

 

STDAPI DllCanUnloadNow(void)

{

return (_Module.GetLockCount()==0)

? S_OK : S_FALSE;

}

 

STDAPI DllGetClassObject(REFCLSID

rclsid, REFIID riid, LPVOID* ppv)

{

return _Module.GetClassObject(rclsid,

riid, ppv);

}

 

STDAPI DllRegisterServer(void)

{

// registers

object, typelib and all interfaces in typelib

return _Module.RegisterServer(TRUE);

}

 

STDAPI DllUnregisterServer(void)

{

return _Module.UnregisterServer(TRUE);

}

12.3.    

 

CExeModule in EXE server

An EXE server makes similar use of CComModule. ATL insert

into your project’s stdafx.h the definition of a new class called CExeModule,

which inherits from CComModule and adds a bit EXE-specific code. So an

EXE server has a global variable _Module of class CExeModule.

Then function _tWinMain will call its methods to do all necessary

jobs such as registering information into the System Registry and registering

the class objects into the Running Object Table.

12.4.    

 

Object Map

_Module need specific information about each coclass

contained in the server to do its jobs. Because each coclass has its own set of

“housing” methods to register itself into the System Registry, to create its

own class object, to create an instance of itself, etc, we only need to inform _Module

of the names of all these methods.

ATL stores the names of all “housing” methods of each

coclass in a structure called _ATL_OBJMAP_ENTRY. It stores the

structures of all the server’s coclasses in a static array, and tell _Module

the name of this array when initializing _Module. Each server (DLL and

EXE alike) has one such array.

Some _Module’s method such as UpdateRegistryFromResource

(when called with parameter

This array is defined with the help of object map macros:

 

BEGIN_OBJECT_MAP(ObjectMap)

OBJECT_ENTRY(CLSID_Any1,

CAny1)

OBJECT_ENTRY(CLSID_Any2,

CAny2)

OBJECT_ENTRY(CLSID_Any2,

CAny2)

END_OBJECT_MAP()

¨    Beginning

and end of an object map

BEGIN_OBJECT_MAP

defines the beginning of a static _ATL_OBJMAP_ENTRY array:

 

#define BEGIN_OBJECT_MAP(x)

static _ATL_OBJMAP_ENTRY x[] = {

END_OBJECT_MAP defines an array element with all NULL

values to mark the end of the array:

 

#define END_OBJECT_MAP()

{NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

Structure

_ATL_OBJMAP_ENTRY is defined as:

 

struct _ATL_OBJMAP_ENTRY

{

const CLSID * pclsid;

_ATL_CREATORFUNC * pfnGetClassObject;

_ATL_CREATORFUNC * pfnCreateInstance;

IUnknown * pCF;

DWORD dwRegister;

_ATL_DESCRIPTIONFUNC* pfnGetObjectDescription;

_ATL_CATMAPFUNC * pfnGetCategoryMap;

HRESULT (WINAPI * pfnUpdateRegistry)(BOOL bRegister);

void (WINAPI * pfnObjectMain)(bool

bStarting);

 

HRESULT WINAPI RevokeClassObject()

{

return CoRevokeClassObject(dwRegister);

}

 

HRESULT WINAPI RegisterClassObject(DWORD dwClsContext, DWORD dwFlags)

{

IUnknown* p = NULL;

if (pfnGetClassObject == NULL)

return S_OK;

HRESULT hRes = pfnGetClassObject

(pfnCreateInstance, IID_IUnknown, (LPVOID*) &p);

if (SUCCEEDED(hRes))

hRes = CoRegisterClassObject

(*pclsid, p, dwClsContext, dwFlags, &dwRegister);

if (p != NULL)

p->Release();

return hRes;

}

};

As you can see, it mainly contains data members of function

pointers.

¨    Object

map entries

Each coclass in a server must have an OBJECT_ENTRY

entry in the object map. Each OBJECT_ENTRY macro defines one _ATL_OBJMAP_ENTRY

structure element of the array:

 

#define OBJECT_ENTRY(clsid, class)

{

&clsid,

class::UpdateRegistry,

class::_ClassFactoryCreatorClass::CreateInstance,

class::_CreatorClass::CreateInstance,

NULL,

0,

class::GetObjectDescription,

class::GetCategoryMap,

class::ObjectMain

},

You can see that this macro assigns the function pointers of

the structure with the methods of the corresponding coclass. Therefore all

needed methods of all coclasses in a server are regisered in this _ATL_OBJMAP_ENTRY

array, ready to be used by _Module.

Now we can tell the whole picture:

1.       When

COM calls the exported functions of a DLL server, they simply delegate the

calls to the _Module. When an EXE server is loaded, its _tWinMain

function will also call _Module’s methods to do all the jobs.

2.       When

_Module is initialized, it is told the name of the object map;

3.       When

one of _Module’s methods is called, it goes through the _ATL_OBJMAP_ENTRY

array, calling one of the registered methods of each coclass to do the job.

There is another object map entry macro OBJECT_ENTRY_NON_CREATABLE

that will be discussed later.

12.5.    

 

Registering Server and its Coclasses

As we can see, macro OBJECT_ENTRY points function

pointer pfnUpdateRegistry in structure _ATL_OBJMAP_ENTRY to the

coclass’s UpdateRegistry method.

 

class::UpdateRegistry,

 

¨    When

is this function pointer called

A DLL delegates a call to its DllRegsiterServer or DllUnregisterServer

to _Module’s RegisterServer and UnregisterServer.

An EXE server calls the same methods as a DLL in its _tWinMain:

 

if (lstrcmpi(lpszToken, _T(“UnregServer“))==0)

{

_Module.UpdateRegistryFromResource(IDR_Test1, FALSE);

 

nRet = _Module.UnregisterServer(TRUE);

 

bRun = FALSE;

break;

}

if (lstrcmpi(lpszToken, _T(“RegServer“))==0)

{

//

register server itself

_Module.UpdateRegistryFromResource(IDR_Test1, TRUE);

 

// go

through the object map and register all coclasses

nRet = _Module.RegisterServer(TRUE);

 

bRun = FALSE;

break;

}

Here _tWinMain first calls _Module’s UpdateRegistryFromResource

directly with the resource ID of the server’s own RGS file, to register

information about the server itself. Then it calls _Module’s method RegisterServer

and UnregisterServer to register each coclass.

RegisterServer and UnregsiterServer goes

through the object map and calls each coclass’s function pointer pfnUpdateRegistry,

which points to the coclass’s UpdateRegistry method, which is defined by

some macros.

¨    Four

macros defining UpdateRegistry

A coclass’s UpdateRegistry method is defined by one

of four macros.

First one is the default macro that ATL will adopt for your

project, which uses the resource ID of the coclass’s binary RGS file to call

_Module’s method UpdateRegistryFromResource:

 

#define DECLARE_REGISTRY_RESOURCEID(x)\

static

HRESULT WINAPI UpdateRegistry(BOOL

bRegister)\

{\

return

_Module.UpdateRegistryFromResource(x,

bRegister);\

}

The second macro takes the string name of the

coclass’s RGS file and use it to call another overloaded _Module method UpdateRegistryFromResource:

 

#define DECLARE_REGISTRY_RESOURCE(x)\

static

HRESULT WINAPI UpdateRegistry(BOOL

bRegister)\

{\

return _Module.UpdateRegistryFromResource(_T(#x),

bRegister);\

}

The third macro doesn’t do anything:

 

#define DECLARE_NO_REGISTRY()\

static

HRESULT WINAPI UpdateRegistry(BOOL

/*bRegister*/)\

{return

S_OK;}

The fourth macro do not use the coclass’s RGS file.

It only put some basic information into System Registry:

 

#define DECLARE_REGISTRY(class,

pid, vpid, nid, flags)\

static

HRESULT WINAPI UpdateRegistry(BOOL

bRegister)\

{\

return

_Module.UpdateRegistryClass(GetObjectCLSID(),

pid, vpid, nid,\

flags,

bRegister);\

}

12.6.    

 

Creating Class Factory

Not like in the raw C++ code example where a

separate class factory class is defined for a coclass, in ATL some template

classes are designed to wrap your coclasses and used as class factory classes.

For DLL and EXE server, different template classes are used.

Instead of having different macros that uses different template classes, ATL

defines only one macro DECLARE_CLASSFACTORY and makes use of an #if

structure to provide different options for DLL and EXE servers.

As we can see, macro OBJECT_ENTRY points function

pointer pfnGetClassObject in structure _ATL_OBJMAP_ENTRY to the

coclass’s inner class _ClassFactoryCreatorClass’s CreateInstance

method:

 

class::_ClassFactoryCreatorClass::CreateInstance,

Note that this CreateInstance method is used to

create an instance of the class factory, not the coclass. It returns an IClassFactory

pointer to the caller, who will then call that interface’s CreateInstance

to actually create the coclass instance.

¨    When

is this function pointer called

For a DLL server, when COM calls its exported DllGetClassObject,

the following calls happens:

 

DllGetClassObject =>

_Module.GetClassObject => AtlModuleGetClassObject

AtlModuleGetClassObject will search the object map

for the _ATL_OBJMAP_ENTRY structure of thequeried coclass and call its pfnGetClassObject

pointer. So _Module doesn’t provide service for DllGetClassObject.

An EXE server calls _Module::RegisterClassObjects in

its _tWinMain function, which goes through the object map and calls each

_ATL_OBJMAP_ENTRY structure’s pfnGetClassObject pointer.

In either a DLL or an EXE’s case, when pfnGetClassObject

is called, it is passed another field of the structure, pfnCreateInstance.

You will see why later.

¨    Definition

of _ClassFactoryCreatorClass

_ClassFactoryCreatorClass is defined in CComCoClass

by macro DECLARE_CLASSFACTORY:

 

#if defined(_WINDLL)

| defined(_USRDLL)

#define DECLARE_CLASSFACTORY_EX(cf)

typedef CComCreator< CComObjectCached<cf>

>

_ClassFactoryCreatorClass;

#else

#define DECLARE_CLASSFACTORY_EX(cf)

 

typedef CComCreator< CComObjectNoLock<cf>

>

_ClassFactoryCreatorClass;

#endif

 

#define DECLARE_CLASSFACTORY()

DECLARE_CLASSFACTORY_EX(CComClassFactory)

You can see that the definition of _ClassFactoryCreatorClass

depends on whether _WINDLL or _USRDLL is defined. If your ATL

project is a DLL project, in “Project” | “Settings” | “C\C++” with category

“Preprocessor”, you will see _USRDLL defined.

Therefore, for a DLL project, _ClassFactoryCreatorClass

becomes

 

CComCreator< CComObjectCached<CComClassFactory> >

and for an EXE project, _ClassFactoryCreatorClass

becomes

 

CComCreator< CComObjectNoLock<CComClassFactory> >

¨    CComCreator<>

Template class CComCreator has only one method CreateInstance

(the one pointed by the pfnGetClassObject pointer in _ATL_OBJMAP_ENTRY

structure in the object map). It is used to create an instance of the

template parameter class. It has the same signature as IClassFactory::CreateInstance.

It simply creates an instance of the parameter class and calls its QueryInterface

with the passed IID and interface pointer. So CComCreator is a very

general-purpose class.

When CComCreator<>::CreateInstance is called by

a DLL or EXE through object map structure field pfnGetClassObject,

another field of the structure pfnCreateInstance pointing to the

coclass-creating function is passed as the first void * parameter. CComCreator<>::CreateInstance

calls the class factory’s SetVoid to pass this pointer to it.

Method SetVoid is defined in CComObjectRootBase

as one of the three ATL creator hook methods (the other two is InternalFinalConstructAddRef

and InternalFinalConstructRelease). It is also a very general-purpose

method which is used to pass the first void * parameter to the parameter

class.

 

template <class T1>

class CComCreator

{

public:

//

pv is function pointer pfnCreateInstance

static HRESULT WINAPI CreateInstance

(void * pv, REFIID riid, LPVOID* ppv)

{

ATLASSERT(*ppv == NULL);

HRESULT hRes = E_OUTOFMEMORY;

T1* p = NULL;

ATLTRY(p = new T1(pv))

if (p != NULL)

{

p->SetVoid(pv);

p->InternalFinalConstructAddRef();

hRes = p->FinalConstruct();

p->InternalFinalConstructRelease();

if (hRes == S_OK)

hRes = p->QueryInterface(riid,

ppv);

if (hRes != S_OK)

delete p;

}

return hRes;

}

};

¨    CComObjectCached

and CComObjectNoLock

Template class CComObjectCached and CComObjectNoLock

are fully functional class factories. CComCreator::CreateInstance

creates an instance of them and calls their QueryInterface for an

interface pointer which is supposed to be IClassFactory. They provide

different implementation for the three IUnknown methods themselves, and

inherit from CComClassFactory the standard class factory functionality.

The reason to use two different classes as class factories

for DLL and EXE server is that class factories in EXE do not affect any

reference count, while class factories in DLL affects both their own and the

server’s reference count.

¨    CComClassFactory

 

class CComClassFactory

:

public IClassFactory,

public CComObjectRootEx<CComGlobalsThreadModel>

{

public:

BEGIN_COM_MAP(CComClassFactory)

COM_INTERFACE_ENTRY(IClassFactory)

END_COM_MAP()

 

//

Implementation of IClassFactory::CreateInstance

STDMETHOD(CreateInstance)

(LPUNKNOWN pUnkOuter, REFIID riid, void** ppvObj)

{

ATLASSERT(m_pfnCreateInstance != NULL);

HRESULT hRes = E_POINTER;

if (ppvObj != NULL)

{

*ppvObj = NULL;

// can’t ask for anything other than IUnknown when aggregating

 

if ((pUnkOuter != NULL) &&

!InlineIsEqualUnknown(riid))

{

ATLTRACE2(atlTraceCOM, 0,

_T(“Asking for non

IUnknown interface”));

hRes = CLASS_E_NOAGGREGATION;

}

else

hRes = m_pfnCreateInstance(pUnkOuter,

riid, ppvObj);

}

return hRes;

}

 

STDMETHOD(LockServer)(BOOL fLock)

{

if (fLock)

_Module.Lock();

else

_Module.Unlock();

return S_OK;

}

 

void SetVoid(void* pv)// defined in CComObjectRootEx

{

m_pfnCreateInstance = (_ATL_CREATORFUNC*)pv;

}

 

_ATL_CREATORFUNC *

m_pfnCreateInstance;

};

Notice that CComClassFactory::CreateInstance actually

does not create the coclass instance by itself. It simply calls the stored

function pointer m_pfnCreateInstance.

¨    High-level

abstraction

You can see that all the above-discussed

class-factory-related classes – CComCreator, CComObjectCached, CComObjectNoLoc

and CComClassFactory are totally abstracted from details on how to

create the coclass instance, which is handled by another coclass method pointed

by another function pointer in the coclass’s corresponding _ATL_OBJMAP_ENTRY

structure entry in the object map.

A sophisticated class factory which knows NOTHING about how

to assemble the coclass instance, sounds funny, isn’t? But it is just because

of such different levels of abstraction a design is made portable.

¨    Customizing

your class factory

From the definition of DECLARE_CLASSFACTORY you can

see, if you use DECLARE_CLASSFACTORY_EX, you can replace the CComClassFactory

with the name of a customized class factory which inherits from

CComClassFactory.

Especially, the customized class factory can be template class

CComClassFactory2<>, which takes a license-validating class as

template parameter, so that the client can not create an instance of your

coclass (such as an ActiveX) using this class factory until you supply a

license file (*.lic).

To add one more level of abstraction, macro DECLARE_CLASSFACTORY2

does the same job:

 

#define DECLARE_CLASSFACTORY2(lic)

 

DECLARE_CLASSFACTORY_EX(CComClassFactory2<lic>)

¨    Singleton

class factory

If you replace the standard DECLARE_CLASSFACTORY

macro with macro DECLARE_CLASSFACTORY_SINGLETON passing the name of your

coclass, then your coclass will only have one instance created in spite of how

many clients there are.

Remember if the server is a DLL the coclass instance is only

unique within the same process. If the server is an EXE the coclass instance is

only unique in the same machine.

12.7.    

 

Coclass Creation with DECLARE_AGGREGATABLE

Macros

We have known that the ATL coclass created by the ATL Object

Wizard is an abstract class which leaves out the IUnknown implementation.

Now you can see that it is also abstracted from how aggregation support is

provided. Some template classes are used to wrap around your coclass to add the

aggregation support.

As we said before, CComCreator<>::CreateInstance

returns an IClassFactory pointer to a class factory, whose CreateInstance

calls another structure field pfnCreateInstance to create the coclass

instance. Now function pointer pfnCreateInstance is filled by macro OBJECT_ENTRY

with

 

class::_CreatorClass::CreateInstance,

There are three macros which defines _CreatorClass in

three different ways. The default one DECLARE_AGGREGATABLE is defined in

CComCoClass. If you choose “Yes” as aggregation option in ATL object

wizard this macro is what you get. If you choose “No”, ATL will insert a DECLARE_NOT_AGGREGATABLE

macro in your coclass definition, which will override the default one. If you

choose “Only”, DECLARE_ONLY_AGGREGATABLE will be inserted.

If you want to change the aggregation behaviour of your

coclass after it is designed, simply change the macro.

¨    DECLARE_AGGREGATABLE

DECLARE_AGGREGATABLE

extends to

 

#define DECLARE_AGGREGATABLE(x)

public:\

typedef CComCreator2< \

CComCreator< CComObject< x

> >, CComCreator<

CComAggObject< x > > \

> _CreatorClass;

The coclass to be created is passed as template parameter

x.CComCreator<

CComObject<x> >is used when the coclass is used as an aggregating

outer object, while CComCreator< CComAggObject<x> > is used

when the coclass is used as an inner object. Both of them are passed into CComCreator2

which can create either an outer or an inner object.

DllGetClassObject or CComModule::RegisterClassObjects

calls CComCreator<>::CreateInstance for an IClassFactory

pointer. Then the caller will call IClassFactory::CreateInstance, which

is implemented by CComClassFactory, which simply directs the call to pfnCreateInstance:

 

hRes = m_pfnCreateInstance(pUnkOuter, riid,

ppvObj);

Depending on whether the pUnkOuter is NULL, CComCreator2

will decide whether to use CComCreator< CComObject<x> > or CComCreator<

CComAggObject<x> >.

¨    DECLARE_NOT_AGGREGATABLE

 

#define DECLARE_NOT_AGGREGATABLE(x)

public:\

typedef CComCreator2<

CComCreator< CComObject< x > >, CComFailCreator<CLASS_E_NOAGGREGATION>

 

> _CreatorClass;

¨    DECLARE_ONLY_AGGREGATABLE

 

#define DECLARE_ONLY_AGGREGATABLE(x)

public:\

typedef CComCreator2<

CComFailCreator<E_FAIL>, CComCreator<

CComAggObject< x > > > _CreatorClass;

12.8.    

 

COM Category

A coclass can declare itself to belong to a COM category. A COM

category contains a group of coclasses that have similar characteristics in

some aspect. Each COM category has a GUID, and all categories are registered

under System Registry entry HKCR/CLSID.

As we already know, one of the object map structure’s filed

is pfnGetCategoryMap, which is filled by macro OBJECT_ENTRY with

 

class::GetCategoryMap,

\

During the registration process, _Module will call

each coclass’s GetCategoryMap to acquire all COM categories that this

coclass supports, and put them under the coclass’s HKCR\CLSID\<guid>

entry.

CComCoClass<> defines a default implementation

of this method which simply returns NULL, indicating that this coclass does not

support any COM category. If your coclass supports one or more categories, you

should declare each of them in a category map which should be placed in your

coclass’s class definition:

 

class ATL_NO_VTABLE CAny

{

BEGIN_CATEGORY_MAP(CAny)

IMPLEMENTED_CATEGORY(CATID_USELESS)

REQUIRED_CATEGORY(CATID_JUST_KIDDING)

END_CATEGORY_MAP()

};

These macros defines the implementation of method GetCategoryMap,

and inside this method an array of _ATL_CATMAP_ENTRY structures, which

contains two fields indicating the type and guid of the supported category:

 

// <atlcom.h>

#define BEGIN_CATEGORY_MAP(x)\

static const struct _ATL_CATMAP_ENTRY

* GetCategoryMap() {\

static const struct _ATL_CATMAP_ENTRY pMap[] = {

 

#define IMPLEMENTED_CATEGORY(

catid ) \

{ _ATL_CATMAP_ENTRY_IMPLEMENTED, &catid },

 

#define REQUIRED_CATEGORY(

catid ) \

{ _ATL_CATMAP_ENTRY_REQUIRED, &catid },

 

#define END_CATEGORY_MAP()\

{ _ATL_CATMAP_ENTRY_END, NULL } };\

return( pMap ); }

 

 

// <atlbase.h>

#define _ATL_CATMAP_ENTRY_END0

#define _ATL_CATMAP_ENTRY_IMPLEMENTED1

#define _ATL_CATMAP_ENTRY_REQUIRED2

 

struct _ATL_CATMAP_ENTRY

{

int

iType;

const CATID* pcatid;

};

To register a COM category, you must do the following two

things:

1.       Define

the guid of the category in a header file, and include that header file

wherever it is referred to:

 

// {BE3875E1-093B-11d6-9867-EF070508BA30}

static const GUID CATID_USELESS

=

{ 0xbe3875e1, 0x93b, 0x11d6, { 0×98, 0×67,

0xef, 0×7, 0×5, 0×8, 0xba, 0×30 } };

 

// {FCFA7081-093B-11d6-9867-EF070508BA30}

static const GUID CATID_JUST_KIDDING

=

{ 0xfcfa7081, 0x93b, 0x11d6, { 0×98, 0×67,

0xef, 0×7, 0×5, 0×8, 0xba, 0×30 } };

2.       Add

the following lines in a server’s RGS file. When the server is compiled and

registered, the new entry for the category will be added into the System

Registry:

 

HKCR

{

NoRemove ‘Component Categories’

{

{ BE3875E1-093B-11d6-9867-EF070508BA30}

{

val 409 = s ‘Useless Objects’

}

}

12.9.    

 

Non-Creatable Coclass

Sometimes you have a coclass that you want the clients know

about (it means that the coclass should have its registry entries), but do not

want clients to directly create (it means that the pfnGetClassObject and

pfnCreateInstance fields of the object map entry structure should be

NULL). You declare such a coclass in the object map using macro OBJECT_ENTRY_NOT_CREATABLE

instead of OBJECT_ENTRY:

 

#define OBJECT_ENTRY_NON_CREATEABLE(class)

 

{

&CLSID_NULL,

class::UpdateRegistry,

NULL,

NULL,

NULL,

0,

NULL,

class::GetCategoryMap,

class::ObjectMain

 

},

 

13.Error Processing and Debugging

13.1.    

 

COM’s Error Handling

A COM server has two ways to generate error information:

1.        

HRESULT – most COM Server methods and COM

library API functions return a HRESULT carrying basic error information.

When you get a HRESULT error code you can look up its description from

VC’s Tools menu. You can also call Win32 function FormatMessage with the

error code, which return the description. Error information carried by HRESULT

is very limited.

2.        

IErrorInfo object – Extra error

information can be passed using error object which implements IErrorInfo

interface.

For non-dispatch function calls, IErrorInfo

object is retrived by client through API functions. For a call to IDispatch::Invoke,

IErrorInfo is returned through EXCEPINFO structure.

For server-side programming which is mainly generating the

error information, the overloaded CComCoClass::Error methods simplifies

the process to deal with IErrorInfo error object.

For client-side programming which is to retrieve error

information, VC’s smart pointer classes and the _com_error exception

which they throw simplifies the error processing with HRESULT and IErrorInfo,

while class COleDispatchDriver and the COleDispatchException

which it throws simplifies error processing with EXCEPINFO.

Note that the COM server does not throw exceptions. It is

the helper classes such as smart pointers and COleDispatchDriver which

throw exceptions.

13.2.    

 

HRESULT

HRESULT may take many possible values:

1.        

S_OK – Success. Value is 0.

2.        

S_FALSE – Function works fine, and the

returned boolean is false. Value is 1.

3.        

E_NOINTERFACE – The QueryInterface

function did not recognize the requested interface. The interface is not

supported.

4.        

E_NOTIMPL – The function contains no

implementation.

5.        

E_FAIL – An unspecified failure has

occurred.

6.        

E_OUTOFMEMORY – The function failed to

allocate necessary memory.

7.        

E_POINTER – Invalid pointer.

8.        

E_INVALIDARG – One or more arguments are

invalid.

9.        

E_UNEXPECTED – A catastrophic failure has

occurred.

10.     

E_HANDLE – Invalid handle.

11.     

E_ABORT – Operation aborted.

12.     

DISP_E_BADPARAMCOUNT – The number of

elements provided to DISPPARAMS is different from the number of parameters

accepted by the method or property.

13.     

DISP_E_BADVARTYPE – One of the parameters

in rgvarg is not a valid variant type.

14.     

DISP_E_EXCEPTION – The application needs

to raise an exception. In this case, the structure passed in pExcepInfo should

be filled in.

15.     

DISP_E_MEMBERNOTFOUND – The requested

member does not exist, or the call to Invoke tried to set the value of a

read-only property.

16.     

DISP_E_NONAMEDARGS – This implementation

of IDispatch does not support named parameters.

17.     

DISP_E_OVERFLOW – One of the parameters

in rgvarg could not be coerced to the specified type.

18.     

DISP_E_PARAMNOTFOUND – One of the parameter

DISPIDs does not correspond to a parameter on the method. In this case,

puArgErr should be set to the first parameter that contains the error.

19.     

DISP_E_TYPEMISMATCH – One or more of the

parameters could not be coerced. The index within rgvarg of the first parameter

with the incorrect type is returned in the puArgErr parameter.

20.     

DISP_E_UNKNOWNINTERFACE – The interface

identifier passed in riid is not IID_NULL.

21.     

DISP_E_UNKNOWNLCID – The member being

invoked interprets string parameters according to the LCID, and the LCID is not

recognized. If the LCID is not needed to interpret parameters, this error

should not be returned.

22.     

DISP_E_PARAMNOTOPTIONAL – A required

parameter was omitted.

13.3.    

 

How Does Server Creates Error Object

In addition to returning a HRESULT indicating error, a

coclass method can use an error object to pass extra error information to the

client. An error object implements interface IErrorInfo and

ISupportErrorInfoand is created and maintained by the COM run time. The

server can not create an error object in its own address space, instead it

calls a COM API function to let COM to create one error object for it.

If a coclass wants to pass to client an error object,

firstable it has to let the client know (when being asked) that it is able to create

IErrorInfo object, by inheriting from interface ISupportErrorInfo.

In ISupportErrorInfo’s only method InterfaceSupportsErrorInfo,

the server should reply to the client whether the given interface provides

error object:

 

STDMETHODIMP CCar::InterfaceSupportsErrorInfo(REFIID riid)

{

static const IID* arr[] =

{

&IID_IEngine,

&IID_IRegistration,

&IID_IStatus

};

for

(int I = 0; i < sizeof(arr) / sizeof(arr[0]); i++)

{

if (InlineIsEqualGUID(*arr[i],

riid))

return S_OK;

}

return S_FALSE;

}

When a coclass method encounters something abnormal that it

wants to tell the client, it calls API function CreateErrorInfo to ask

COM to create an error object. This API function returns to the server an ICreateErrorInfo

pointer.

Interface ICreateErrorInfo has the following methods:

1.       SetDescription

– returns error description as a BSTR

2.       SetGUID

– returns the guid of the interface that defined the error

3.       SetHelpFile

– returns the path to the corresponding help file.

4.       SetHelpContext

– returns the context ID indentifying a particular entry in the help file.

5.       SetSource

– returns the ProgID of the object that caused the error.

Then the server will call ICreateErrorInfo’s methods

to place error information into the error object. Then the server will query

the ICreateErrorInfo interface to get the error object’s IErrorInfo

interface pointer. Then it will call API function SetErrorInfo passing

the IErrorInfo pointer, to link the error object with the current

thread:

 

if(…)

// Something is wrong!

{

ICreateErrorInfo * pCreate;

HRESULT hr = CreateErrorInfo(&pCreate);

BSTR bstr = SysAllocString(L”Error info created in Client!”);

pCreate->SetDescription(bstr);

IErrorInfo *

pError;

hr

= pCreate->QueryInterface(IID_IErrorInfo, (void **)&pError);

SetErrorInfo(0,

pError);// linking the error object

with the thread

return E_FAIL;

}

COM run time maintains one error object for each thread. A

lately linked error object will overwrite the previously one. Actually both the

client and the server can create, link or retrieve the error object, although

in real life it is always the server which creates and links the error object,

and the client which retrieves.

13.4.    

 

ATL’s Support on Creating Error Object

As said before, an ATL object inherits from CComCoClass,

which has a set of overloaded Error methods, which wrap function calls

such as CreateErrorInfo, SetDescription, QueryInterface

and SetErrorInfo and greatly simplifies your job. A coclass can simply

make a call to one of these methods and return the return value to the client.

One of them looks like:

 

static HRESULT WINAPI Error(

LPCOLESTR lpszDesc,

const IID& iid = GUID_NULL, // Interface defining the error

HRESULT hRes = 0

);

If hRes is nonzero, Error returns hRes.

If it is zero, Error returns DISP_E_EXCEPTION.

A coclass method can simply say:

 

if(…) // error happens

{

return Error(“Not enough

memory!”);

}

13.5.    

 

How Does Client Retrieve Error Object

When client checks the returned HRESULT and finds out that

something is wrong in the server, if he wants more details, he will first query

the present interface for interface ISupportErrorInfo. If the query

returns the interface, he then calls ISupportErrorInfo::InterfaceSupportsErrorInfo

to check whether the interfacewhich

returns error supports error object. If yes, the client knows that there must

be an error object waiting for him to retrieve. So he calls COM API function GetErrorInfo

to get an IErrorInfo interface pointer of the error object. Then he can

call themethods to retrieve the error

information.

If the server method returns S_OK, even if he creates an

error object, it will not be retrieved and checked.

 

if(FAILED(hr))

 

{

ISupportErrorInfo*

pSupport;

hr

= m_pAccount->QueryInterface

 

(IID_ISupportErrorInfo, (void**) &pSupport);

if

(SUCCEEDED(hr))

{

hr = pSupport->InterfaceSupportsErrorInfo(IID_IError);

if (SUCCEEDED(hr))

{

IErrorInfo * pError;

hr = GetErrorInfo(0,

&pError);

BSTR

bstr;

hr = pError->GetDescription(&bstr);

USES_CONVERSION;

::MessageBox(NULL, OLE2CT(bstr),

“Client”, MB_OK);

SysFreeString(bstr);

}

}

}

IErrorInfo has the following methods:

1.       GetDescription

– returns error description as a BSTR

2.       GetGUID

– returns the guid of the interface that defined the error

3.       GetHelpFile

– returns the path to the corresponding help file.

4.       GetHelpContext

– returns the context ID indentifying a particular entry in the help file.

5.       GetSource

– returns the ProgID of the object that caused the error.

13.6.    

 

How Does VB Client Retrieve Error Object

 

Private Sub btnDoIt_Click()

On Error GoTo OOPS‘ Acts like a C++ try

block.

Normal code, in which exception may be thrown

Exit Sub

 

OOPS:

‘ Error handling

code

Resume Next ‘ Resume normal execution at next line of throw point

 

End Sub

13.7.    

 

Smart Pointer Class and Exception _com_error

While CComCoClass simplifies creating an error object at the

server side, VC’s smart pointer class wraps all jobs to retrieve the error

object at the client side, including

1.        

Check whether the returned HRESULT indicates

error;

2.        

Find out whether the coclass supports

IErrorInfo;

3.        

Retrive the error information.

As soon as the smart pointer class finds out that the

returned HRESULT indicates an error, it will throw an exception of type _com_error

containing error information stored in the error object. If server has actually

put extra error information into the error object, a call to _com_error’s

method Description will return the error string which had been set into

the error object by the server with ICreateErrorInfo::SetDescription.

Otherwise the method will simply return NULL.

_com_error has some useful methods such as:

1.       

ErrorMessage: calls Win32 FormatMessage

function, which returns a simple system message description such as “Exception

happened”.

2.       

ErrorInfo: returns the IErrorInfo

interface pointer.

3.       

Description: calls IErrorInfo::GetDescription

and returns a _bstr_t.

Because of the flexibility and power smart pointer classes

and _com_error offer, it is always good to use them instead of a great

number of lower-level calls.

 

try {

m_spEngine->GetTemp();

}

catch(_com_error

& ex)

{

char buf[80];

sprintf(buf, “Error happened in IEngine::GetTemp. %S”,

ex.Description());

msg(buf);

}

13.8.     Error Information in ATL‘s Automation

VC’s smart pointer classes can NOT invoke the IDispatch

interface.

In ATL, IDispatch::Invoke is implemented by IDispatchImpl<>.

When you call a server method through IDispatch::Invoke, the

implementation will check for the returned HRESULT. If it indicates an error

(e.g. E_FAIL), it will retrieve the error object, put the error information

into structure EXCEPINFO, and return it to the client. If the called

method did not create the error object, the fields of EXCEPINFO will be

NULL.

EXCEPINFO is defined as follow:

 

typedef struct tagEXCEPINFO {

WORDwCode;

WORDwReserved;

BSTRbstrSource;

BSTRbstrDescription;

BSTRbstrHelpFile;

DWORD dwHelpContext;

PVOID pvReserved;

HRESULT (__stdcall *pfnDeferredFillIn)(struct tagEXCEPINFO *);

SCODE scode;

} EXCEPINFO, * LPEXCEPINFO;

Example of retrieving error information from structure

EXCEPINFO:

int main(int argc, char* argv[])

{

CoInitialize(NULL);

 

IAny * pAny;

IDispatch * pDisp;

HRESULT hr;

hr

= CoCreateInstance

(CLSID_Any, NULL, CLSCTX_SERVER, IID_IAny, (void **) &pAny);

hr

= pAny->QueryInterface(IID_IDispatch, (void**) &pDisp);

 

LONG lResult;

char buf[80];

EXCEPINFO * pExcepInfo = new EXCEPINFO;

DISPPARAMS params;

 

params.rgvarg = new VARIANTARG[2];

params.rgvarg[0].vt

= VT_I4 | VT_BYREF;

params.rgvarg[0].plVal = &lResult;

 

params.rgvarg[1].vt = VT_I4;

params.rgvarg[1].lVal = 1234L;

 

params.cArgs = 2;

params.cNamedArgs = 0;

 

hr

= pDisp->Invoke(

1,// disp ID for method Test

IID_NULL,//

reserved for future use

LOCALE_SYSTEM_DEFAULT,// locale

context

DISPATCH_METHOD,//

context of the Invoke call

&params,// DISPPARAMS

structure used to convey parameters

NULL,// VARIANTARG *, used

to receive result

pExcepInfo,// EXCEPTION *, used to hold exception

info

NULL// The index of the

first error argument

);// in rgvarg VARIANTARG

array

 

if

(FAILED(hr))

{

sprintf(buf, “Error description: %S \n\n”,

excepInfo.bstrDescription);

printf(buf);

printf(“IDispatch:Invoke failed\n”);

}

 

sprintf(buf, “2 x 1234 = %d\n\n”, lResult);

printf(buf);

 

CoUninitialize();

return 0;

}

13.9.    

 

Debugging EXE Server

For DLL servers, because they are in process, they can be

debugged as normal in-process code. For EXE servers:

1.        

Open the server project in VC, set a break

point, start debug by pressing “F5″. This will start the server.

2.        

Run the client program. When the call to the

method of the server containing the break point is made, the control will step

into the server program and stop at the break point.

14.Automation with IDispatch Interface

14.1.    

 

Why IDispatch

As you can see from section “Virtual Function Call Using

vPtr and vtable” of chapter “vPtr and vtable” of my C++ tutorial, an interface

pointer returned by a COM component points to the vPtr of the interface

in the coclass object’s memory footprint.

If you want to make use of the vPtr, you have to use

a language with a compilation facility, and the type of the interface vPtr

must be known at compile time, so that the compiler can read the definition of

the interface to find out the structure of its vtable, then generate

some code and add it together with the original code that the programmer has

written, to go through the vPtr and vtable and find the proper

implementation of an interface method.

However, for script languages such as JavaScript and early

versions of VB, there is no compilation process, and therefore the client can

not generate code to utilize the vPtr and the vtable. To enable

script languages to invoke itself, a COM component should support IDispatch

interface. And of course, a script language that wants to invoke a COM

component should know the type definition of IUnknown and IDispatch

and have already generated the code to walk through their vtables.

Then, through a not-complicated mechanism, by using generic

type VARIANT, the script language can find out through IDispatch

all necessary information about any other custom interfaces the COM component

supports, and call any of its methods.

14.2.    

 

IDispatch Methods

If a coclass wants to be used by script languages, it should

support interface IDispatch. IDispatch interface has four

methods:

1.   

GetTypeInfoCount: check whether the

server supports type information interface ITypeInfo.

2.   

GetTypeInfo: retrieve an ITypeInfo

pointer from the server, so as to access the type info contained in the type

library.

The above two methods are normally not used, unless you want

to design an application such as the OLE/COM Object Viewer.

3.   

GetIDsOfNames: retrieve the DISPID

(typedef of long) of one or several methods given their string names:

 

HRESULT GetIDsOfNames(

REFIID riid,// Reserved for future use; set to

IID_NULL

OLECHAR ** rgszNames, // array of method

names to be mapped

unsigned int cNames,// Count of the names to be mapped.

LCID lcid,// Locale context

DISPID * rgDispId);// disp ID for the method names

4.   

Invoke: using the DISPID of a method,

call this method:

 

HRESULTInvoke(

DISPID dispId,

REFIID riid,//

reservered for future use, pass IID_NULL

LCIDlcid,// Language ID (English, Arabic, etc.)

// Can be

LOCALE_SYSTEM_DEFAULT

WORDwFlags,

// DISPATCH_METHOD, DISPATCH_PROPERTYGET, DISPATCH_PROPERTYPUT

// or DISPATCH_PROPERTYREF

(when the property accepts a

// reference to an object).

DISPPARAMS * pDispParams, // a structure used to pass arguments

VARIANT *pVarResult,// return value

EXCEPINFO *pExcepInfo,// structure containing error info

UINT *puArgErr// index of the error argument in the

VARIANTARG

// array in the

DISPPARAMS structure

);

Normally a server’s IDispatch::GetIDsOfNames will

return 0 for its first method, 1 for the second, 2 for the third, and so on. So

you may even directly call Invoke without calling GetIDsOfNames,

although it is not safe.

The limitation of Invoke is that you can only pass an

array of variants. You can not pass custom types, unless you convert it to a

BSTR and convert it back in the server method.

14.3.    

 

A Simple IDispatch Implementation in Raw C++

The coclass will simply inherit from IDispatch alone,

and implement all its methods (three IUnknown methods and four IDispatch

methods) as private methods which are only called internally by Invoke.

IUnknown methods are implemented as normal, in which QueryInterface

only returns IUnknown and IDispatch interface pointer to clients:

 

STDMETHODIMP CAny::QueryInterface(REFID riid,

void ** ppv)

{

if(riid == IID_IUnknown)

*ppv = (IUnknown *)this;

else if(riid == IID_IDispatch)

*ppv = (IDispatch *)this;

else

{

*ppv = NULL;

return E_NOINTERFACE;

}

 

((IUnknown *)(*ppv))->AddRef();

return S_OK;

}

Method GetIDsOfNames:

 

STDMETHODIMP CAny::GetIDsOfNames(REFIID riid,

OLECHAR ** rgszNames,

unsigned int cNames, LCID lcid, DISPID * rgDispId)

{

else if(_wcsicmp(*gszNames, L(“PrintEmployeeNames”)) == 0)

*rgDispId = 3;

}

Method Invoke:

 

STDMETHODIMP CAny::Invoke(DISPID dispId, REFIID riid, LCID lcid,

WORD wFlags,

DISPPARAMS * pDispParams,

VARIANT * pVarResult, EXCEPINFO * pExcepInfo,

UINT * puArgErr)

{

else if(dispId == 3)

{

PrintEmployeeNames();

}

}

14.4.    

 

DISPPARAMS Structure

This structure is used to pass a number of arguments to Invoke.

 

typedef struct {

VARIANTARG * rgvarg;// Array of arguments.

DISPID * rgdispidNamedArgs; // Dispatch IDs

of named arguments.

// NULL is no

named arguments.

unsigned int cArgs;// Number of arguments.

unsigned int cNamedArgs;// Number of named arguments.

} DISPPARAMS;

 

14.5.    

 

VARIANT Structure

VARIANT is a data type supported by VB and

Microsoft’s universal IDL data type. Except for the reserverd members, it

contains two members: one is a union of all universal IDL data types, the other

is a VARTYPE (typedef of unsigned short) indicating the type of the

union.

To initialize the VARIANT structure, call COM API

function VariantInit.

 

typedef struct STRUCT tagVARIANT VARIANT;

typedef struct STRUCT tagVARIANT VARIANTARG;

 

typedef struct tagVARIANT{

VARTYPE vt;

unsigned short wReserved1;

unsigned short wReserved2;

unsigned short wReserved3;

 

union {

BytebVal;// VT_UI1

ShortiVal;// VT_I2

longlVal;// VT_I4

floatfltVal;// VT_R4

doubledblVal;// VT_R8

VARIANT_BOOLboolVal;// VT_BOOL

SCODEscode;// VT_ERROR

CYcyVal;// VT_CY

DATEdate;// VT_DATE

BSTRbstrVal;// VT_BSTR

DECIMAL *pdecVal// VT_BYREF|VT_DECIMAL

IUnknown *punkVal;// VT_UNKNOWN

IDispatch *pdispVal;// VT_DISPATCH

SAFEARRAY *parray;// VT_ARRAY

 

Byte *pbVal;// VT_BYREF|VT_UI1

short *piVal;// VT_BYREF|VT_I2

long *plVal;// VT_BYREF|VT_I4

float *pfltVal;// VT_BYREF|VT_R4

double *pdblVal;// VT_BYREF|VT_R8

VARIANT_BOOL *pboolVal;// VT_BYREF|VT_BOOL

SCODE *pscode;// VT_BYREF|VT_ERROR

CY *pcyVal;// VT_BYREF|VT_CY

DATE *pdate;// VT_BYREF|VT_DATE

BSTR *pbstrVal;// VT_BYREF|VT_BSTR

IUnknown * *ppunkVal;// VT_BYREF|VT_UNKNOWN

IDispatch * *

ppdispVal;//

VT_BYREF|VT_DISPATCH

SAFEARRAY * *pparray;// VT_BYREF|VT_ARRAY

 

VARIANT *pvarVal;// VT_BYREF|VT_VARIANT

void *byref;// Generic ByRef

charcVal;// VT_I1

unsigned shortuiVal;// VT_UI2

unsigned longulVal;// VT_UI4

intintVal;// VT_INT

unsigned intuintVal;// VT_UINT

char*pcVal;// VT_BYREF|VT_I1

unsigned short*puiVal;// VT_BYREF|VT_UI2

unsigned long*pulVal;

// VT_BYREF|VT_UI4

int*pintVal;// VT_BYREF|VT_INT

unsigned int*puintVal;// VT_BYREF|VT_UINT

};

// union

};

To pass a short number 123 into the server, you

should use pass-by-value:

 

VARIANT v1;

VariantInit(&v1);

v1.vt = VT_I2;

v1.iVal = 123;

To get a long number from the server into a variable

say long lStatus, you should set the “VARTYPE vt” member to VT_BYREF

| VT_I2, and set the “long * plVal” member to &lStatus:

 

VARIANT v1;

VariantInit(&v1);

v1.vt = VT_BYREF | VT_I2;

v1.plVal = &lStatus;

To free up memory of VARIANT v1, use function VariantClear:

 

VariantClear(&v1);

To copy a VARAINT or change type of a VARAINT, use function VariantCopy

and VariantChangeType.

ATL provides a wrapper class CComVariant.

14.6.    

 

COleVariant

COleVariant is wraps a

VARIANT and can be treated or used exactly as a VARIANT. It’s constructor takes

a primitive type as argument. As other wrapper classes, it has a Attach

and Detach method. Note that it does not have operator ++ overloaded.

 

LONG l = 25;

COleVariant Index(0L);

Index = l + 75L;

14.7.    

 

SAFEARRAY

Script languages prefer to use SAFEARRAYs. A SAFEARRAY

can have multiple dimensions, each dimension may start from a non-zero number.

It can also be locked by multiple clients.

 

typedef structtagSAFEARRAY

{

USHORT cDims;// Number of

dimensions of the array

USHORT fFeatures; // Used by COM API function to deallocate memory

ULONG cbElements; // Size of a single element

ULONG cLocks;// Number of

locks imposed on the array

PVOID pvData;// Points to the

actual data buffer

SAFEARRAYBOUND rgsabound[]; // Has cDims elements

}SAFEARRAY;

 

typedef structtagSAFEARRAYBOUND

{

ULONG cElements; // Size of this dimension

LONG lLbound;// Lower bound

(starting subscrip.) of this dimension

}SAFEARRAYBOUND;

You do not directly operate on the structure,

instead you call COM API functions:

SafeArrayCreate,

SafeArrayGetDim,SafeArrayDestroy,SafeArrayGetElement,SafeArrayPutElement, SafeArrayGetUBound,SafeArrayGetLBound,SafeArrayAccessData,SafeArrayUnAccessData.

To create a SAFEARRAY of 5 long

numbers in one dimension and fill them with 0 ~ 4:

 

SAFEARRAYBOUND bound[1] = {5, 0};

SAFEARRAY * psa = SafeArrayCreate(VT_I4, 1,

bound);

 

for(int i = 0; i < 5; i++)

SafeArrayPutElements(psa, &i, &i);

To passing a SAFEARRAY of type long through a

interface method call, you only need to create a single VARIANT, set its

member vt to VT_I4 | VT_ARRAY, and parray to the SAFEARRAY.

14.8.    

 

Dual Interface

A dual interface looks like

 

[

object,

uuid(D10A5C6C-2191-11D6-9867-A5AFD3E0F730),

dual,

helpstring("IAny

Interface"),

pointer_default(unique)

]

interface IAny : IDispatch

{

[id(1), helpstring("method Method1")] HRESULT

Method1(long a, short b);

[id(2), helpstring("method Method2")]

HRESULT Method2(long a, short b);

[id(3), helpstring("method Method3")]

HRESULT Method3(long a, short b);

};

As you can see, there are three differences between a dual

interface and a normal interface:

1.        

It has attribute [dual];

2.        

It inherits not from IUnknown but IDispatch;

3.        

Its methods are marked by “id( )” attributes

indicating the DISPIDs.

In the previous example, a coclass supports nothing but IDispatch.

Therefore clients written in both early-binding and script languages have to go

through IDispatch and suffer low speed and inconvenience. If a coclass

supports a dual interface, a script client can still invoke all methods through

IDispatch, while an early-binding client can make get a vPtr of

the interface and make use of it to directly invoke the methods.

14.9.    

 

Dual Interface Implementation in Raw C++

Compared with the previous

IDispatch-alone implementation, the implementation of a dual interface in raw

C++ has only three differences:

1.       The

coclass inherits from all the dual interfaces (such as IAny) instead of

directly from IDispatch alone;

2.       The

coclass implements the dual interface’s methods as public methods instead of

private, so that early-binding clients can directly call.

3.       QueryInterface

not only return vPtrs ofIDispatch

and IUknown but also all the dual interfaces.

15.Call Back

Architecture

15.1.    

 

Simple Way of Call Back in COM

As we know, when a server knows what to do (the interface

and the method signature) but don’t know how (the implementation of the

method), it uses call back technique.

In COM, there is a straight-forward and simple way to do

call back.

In the server’s IDL file, define a call-back interface with

all needed call-back methods. Make sure the interface belongs to a coclass in

the server. This coclass can be any one, so we can choose the coclass which

needs to call the call-back methods.

Then, add a member as a pointer of the call-back interface

to the coclass. In the working interface in the coclass whose methods needs to

call the call-back methods, add a method called something like “Advise

to set the member pointer. The methods of the working interface can then call

the call-back methods through the member pointer. The design of the server ends

here. This coclass which contains the working interface and the call-back

interface is called the “source”.

Then we need to provide another server which implements the

call-back interface. Simply use the “Implement Interface Wizard” to implement

the call-back interface in a coclass. This implementing coclass is called the “Sink”.

After we finish the source and the sink, the client can call

CoCreateInstance to acquire a pointer to the working interface from the

source, and a pointer to the call-back interface from the sink which implements

the call-back interface, then call the working interface’s Advise method

to pass it the call-back interface pointer. Then we can call the working

methods of the working interface.

Exmaple:

 

//*************** Sink

server’s IDL ********************

import “oaidl.idl”;

import “ocidl.idl”;

 

[

object,

uuid(4FD136E1-E1E1-11d5-9866-BA05D2378030),

dual,

helpstring("ICallBack Interface"),

pointer_default(unique)

]

interface ICallBack :

IDispatch// call-back interface

{

[id(1), helpstring("method SayHi")]

HRESULT SayHi();// call-back method

};

 

[

object,

uuid(B14C770C-E1E0-11D5-9866-BA05D2378030),

dual,

helpstring("IFoo Interface"),

pointer_default(unique)

]

interface IWork : IDispatch// working interface

{

[id(1), helpstring("method Advise")]

HRESULT Advise([in] ICallBack * pCallBack);

 

[id(2), helpstring("method CallBack")]

HRESULT DoWork();// working method

};

 

[

uuid(B14C7700-E1E0-11D5-9866-BA05D2378030),

version(1.0),

helpstring("Threader 1.0 Type Library")

]

library THREADERLib

{

importlib(“stdole32.tlb”);

importlib(“stdole2.tlb”);

 

[

uuid(B14C770D-E1E0-11D5-9866-BA05D2378030),

helpstring("Foo Class")

]

coclass Foo

{

[default] interface IWork;

interface ICallBack;

};

};

 

 

//**************** Sink

IDL ********************

//Fully empty, has nothing to do with the

call-back interface

import “oaidl.idl”;

import “ocidl.idl”;

[

object,

uuid(B14C771A-E1E0-11D5-9866-BA05D2378030),

dual,

helpstring("IBar Interface"),

pointer_default(unique)

]

interface

IBar : IDispatch

{

};

 

[

uuid(B14C770E-E1E0-11D5-9866-BA05D2378030),

version(1.0),

helpstring("Impl 1.0 Type Library")

]

library IMPLLib

{

importlib(“stdole32.tlb”);

importlib(“stdole2.tlb”);

 

[

uuid(B14C771B-E1E0-11D5-9866-BA05D2378030),

helpstring("Bar Class")

]

coclass Bar

{

[default] interface IBar;

};

};

 

 

//*******************

Sink class header file ******************

#ifndef __FOO_H_

#define __FOO_H_

 

#include “resource.h”

 

class ATL_NO_VTABLE CFoo :

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<CFoo, &CLSID_Foo>,

public IDispatchImpl<IWork, &IID_IWork,

&LIBID_THREADERLib>

{

public:

CFoo()

{

}

 

DECLARE_REGISTRY_RESOURCEID(IDR_FOO)

 

DECLARE_PROTECT_FINAL_CONSTRUCT()

 

BEGIN_COM_MAP(CFoo)

COM_INTERFACE_ENTRY(IWork)

COM_INTERFACE_ENTRY(IDispatch)

END_COM_MAP()

 

public:

STDMETHOD(DoWork)()

{

m_pCallBack->SayHi();

return S_OK;

}

 

STDMETHOD(Advise)(ICallBack

* pCallBack)

{

m_pCallBack = pCallBack;

return S_OK;

}

 

private:

ICallBack * m_pCallBack;

};

 

#endif

 

 

//************** Sink

head file ******************

#ifndef __BAR_H_

#define __BAR_H_

 

#include “resource.h”// main symbols

#import “C:\MY

DOCUMENTS\FRANK\VC6\THREADER\DEBUG\THREADER.DLL” \

raw_interfaces_only, raw_native_types, no_namespace, named_guids

 

class ATL_NO_VTABLE CBar :

public CComObjectRootEx<CComSingleThreadModel>,

public

CComCoClass<CBar, &CLSID_Bar>,

public IDispatchImpl<IBar, &IID_IBar, &LIBID_IMPLLib>,

public IDispatchImpl<ICallBack, &IID_ICallBack,

&LIBID_THREADERLib>

{

public:

CBar()

{

}

 

DECLARE_REGISTRY_RESOURCEID(IDR_BAR)

 

DECLARE_PROTECT_FINAL_CONSTRUCT()

 

BEGIN_COM_MAP(CBar)

COM_INTERFACE_ENTRY(IBar)

COM_INTERFACE_ENTRY2(IDispatch, IBar)

COM_INTERFACE_ENTRY(ICallBack)

END_COM_MAP()

 

public:

STDMETHOD(SayHi)()// call-back method implementation

{

m(“Hi from server Impl!”);

return S_OK;

}

};

 

#endif

 

 

//*******************

Client ***********************

CoInitializeEx(NULL, COINIT_MULTITHREADED);

 

HRESULT hr =

CoCreateInstance

(CLSID_Foo, NULL, CLSCTX_SERVER, IID_IWork,

(void **)&g_pWork);

 

hr

= CoCreateInstance

(CLSID_Bar, NULL, CLSCTX_SERVER,

IID_ICallBack, (void

**)&g_pCallBack);

 

hr

= g_pWork->Advise(g_pCallBack);

hr

= g_pWork->DoWork();

 

CoUninitialize();

 

16.Some

Individual Topics

16.1.    

 

Multiple COM Instances and Singleton Class

Factory

For all COM servers created by VC’s AppWizard, both DLL and

EXE servers, there is one class factory for each of the coclasses, which is

used to create an instance of the coclass.

For DLL server, because the server code resides in the same

process of the client, if there are two client processes invoking the same

server, there will be two copies of server code in the two processes. In

comparison, because EXE server lives in a separate and independent process, and

all its class factories once created are registered in the running object

table, if there are multiple client processes invoking the same server, only

one server process will be started, and this server will serve all client

processes.

Normally, when a client calls CoCreateInstances for

the same coclass for multiple times, multiple instances of that coclass will be

created by the server and live in that server. This is true for both DLL and

EXE server.

If you want to make sure that only one instance is created

from a coclass, you can put macro

 

DECLARE_CLASSFACTORY_SINGLETON(ClassName)

in the class definition. Then when CoCreateInstance

is called multiple times in the same process (for DLL server) or even in

multiple processes (for EXE server), only the first call creates an instance of

that coclass, and the rest gets a reference to the same instance.

This macro can only be used safely in a single-thread

application. For a multiple-thread application, it will cause problems.

16.1.    

 

Avoiding Interface Method Name Clash

Suppose coclass CAny implements two interfaces IInterf1

and IInterf2, each with a method Clashing with the same

signature, then the coclass can only provide one implementation of the clashing

method to be shared by the two interfaces. Some times it is not desireable.

¨    Solution

1:substituting inheritance with

aggregation

Create a classes implementing one of the two interfaces with

clashing methods:

 

struct Interf2Impl

: public IInterf2 {

STDMETHODIMP_(ULONG)

AddRef();

STDMETHODIMP_(ULONG) Release();

STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);

 

STDMETHODIMP Clashing();

 

CAny * m_pParent;

};

In Interf2Impl, the three IUnknown methods

will simply turn and call CAny’s implementations through the CAny *

m_pParent. It does not need its own implementation of the three IUnknown

methods because this class is used as a member object and does not need its own

life-time control. All this class is doing is to provide its own implementation

for the clashing method.

Coclass CAny no longer implements the interface which

has been implemented by class Interf2Impl. Instead it holds a member

object of Interf2Impl:

 

class CAny

: public IInterf1 {

public:

STDMETHODIMP_(ULONG) AddRef();

STDMETHODIMP_(ULONG) Release();

STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);

 

STDMETHODIMP Cashing();

 

private:

ULONG m_refCount;

Interf2Impl m_impl;

};

The implementation of AddRef and Release in CAny

is as normal. QueryInterface will simply cast the member object to its

implemented interface and return it to the client:

 

STDMETHODIMP CAny::QueryInterface(REFIID

riid, void ** ppv)

{

else if(riid == IID_IInterf2)

{

*ppv = (IInterf1 *)&m_impl;

}

}

In CAny’s constructor, it will set the member

object’s parent pointer to itself:

 

m_impl.m_pParent = this;

Interf2Impl can be a stand-alone class. To make the

design more cohesive, we can also make it an inner class of the coclass CAny.

¨    Solution

2:implement the methods separately

before they clash

Create two wrapper classes to inherit from the two clashing

interfaces. Each wrapper class only implements the name-clashing method,

leaving all other methods to be implemented by the coclass as usual. Therefore

these wrapper classes are still abstract classes:

 

#include “stdafx.h”

#include “unknwn.h”

#include “winerror.h”

#include <windows.h>

#include <initguid.h> // contains

definition of DEFINE_GUID

 

// {211FE1E1-FA21-11d5-9867-9880AB8D8130}

DEFINE_GUID(IID_IInterf1, 0x211fe1e1, 0xfa21,

0x11d5, 0×98, 0×67, 0×98, 0×80, 0xab, 0x8d, 0×81, 0×30);

 

// {211FE1E2-FA21-11d5-9867-9880AB8D8130}

DEFINE_GUID(IID_IInterf2, 0x211fe1e2, 0xfa21,

0x11d5, 0×98, 0×67, 0×98, 0×80, 0xab, 0x8d, 0×81, 0×30);

 

interface IInterf1

: public IUnknown {

STDMETHOD(Hi)()

PURE;

};

 

interface IInterf2: public IUnknown {

STDMETHOD(Hi)()

PURE;

};

 

struct Interf1Impl

: public IInterf1 {

STDMETHODIMP

Hi()

{

printf(“Hi

from Interf1Impl!\n”);

return

S_OK;

}

};

 

struct Interf2Impl

: public IInterf2 {

STDMETHODIMP

Hi()

{

printf(“Hi

from Interf2Impl!\n”);

return

S_OK;

}

};

Coclass CAny will inherit from the wrapper classes

instead of the raw interfaces, and the rest of CAny remains just as

normal:

 

class CAny

: public Interf1Impl, public Interf2Impl {

public:

CAny() : m_refCount(0) {}

 

STDMETHODIMP_(ULONG) AddRef()

{

return ++m_refCount;

}

 

STDMETHODIMP_(ULONG) Release()

{

if(–m_refCount == 0)

{

delete this;

return 0;

}

else

return m_refCount;

}

 

STDMETHODIMP QueryInterface(REFIID riid, void ** ppv)

{

if(riid == IID_IUnknown)

{

// cast to IUnknown * through IInterf1 * to avoid ambiguity

*ppv = (IUnknown *)(IInterf1 *)this;

}

else if(riid == IID_IInterf1)

{

*ppv = (IInterf1 *)this;

}

else if(riid == IID_IInterf2)

{

*ppv = (IInterf2 *)this;

}

else

{

*ppv = NULL;

return E_NOINTERFACE;

}

 

return S_OK;

}

 

private:

ULONG m_refCount;

};

In this approach, the wrapper class does not implement any

other method of the inherited interface except for the clashing one, and is

therefore much simpler. The coclass itself also experiences very limitted

change. So this approach is much better than the previous one. It can also be

directly applied in ATL. Using the same Interf1Impl and Interf2Impl,

CAny represented in ATL looks like:

class ATL_NO_VTABLE CAny :

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<CAny, &CLSID_Any>,

public Interf1Impl, // Instead of

IInterf1

public Interf2Impl// Instead of IInterf2

{

public:

CAny()

{

}

 

DECLARE_REGISTRY_RESOURCEID(IDR_ANY)

DECLARE_NOT_AGGREGATABLE(CAny)

 

DECLARE_PROTECT_FINAL_CONSTRUCT()

 

BEGIN_COM_MAP(CAny)

COM_INTERFACE_ENTRY(IInterf1)

COM_INTERFACE_ENTRY(IInterf2)

END_COM_MAP()

};

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>