COM & ATL

Silan Liu

 

1.      COM Basics

1.1.        Service Control Manager (SCM)

A Windows program called Service Control Manager (SCM) does most of the job in COM server invoking. It finds the server through System Registry, runs it, has it create the COM object, sets up local/remote transparency, and returns an interface pointer to the client.

Then the client can directly invoke methods of the COM object through the pointer, and there is no middleware involved unless it is a remote server, in which case only RPC is involved.

1.2.        Interface IUnknown

All COM interfaces should inherit interface IUnknown:

 

interface IUnknown

{

HRESULT QueryInterface(REFIID iid, void** ppvObject);

ULONG AddRef();

ULONG Release();

}

One COM object may be used by multiple clients. The COM server maintains a reference count for each interface of the object. When one client asks to create an instance of the interface with CoCreateInstance or CoCreateInstanceEx, the COM server will call that interface’s method AddRef to increment the reference count.

When one client is finished with an interface, it should call its Release method to decrement the reference count. When the count reaches zero, the COM server should destroy the COM object.

In Visual Basic, a local COM object is automatically destroyed when leaving scope. To manually delete an object, say

 

Set objAccount = Nothing

1.3.        Global Unique ID (GUID)

Each coclass and interface has its guid, which is a 128-bit number. For easy use by the programmer, an easy-remember manifest constant is defined for each guid. In Visual C++ these constants are defined in a header file e.g. “Bank_i.c”, and some entries look like:

 

const IID IID_IGreet = {0x7A5E6E81,0x3DF8,0x11D3,{0x90,0x3D,0x00,0x10,0x5A,0xA4,0x5B,0xDC}};

 

const IID LIBID_BANKLib = {0x0FFBDAA1,0xFCA7,0x11D2,{0x8F,0xF4,0x00,0x10,0x5A,0xA4,0x5B,0xDC}};

 

const CLSID CLSID_Account = {0x0FFBDAAE,0xFCA7,0x11D2,{0x8F,0xF4,0x00,0x10,0x5A,0xA4,0x5B,0xDC}};

GUID constants such as IID_IGreet and CLSID_Account are not globally unique, but it does not matter because they live only in the scope of one application program. The compiler will read the interface definition file to convert the constant to the corresponding guid.

GUIDs for classes are of type CLSID, and all variables start with “CLSID_” followed by the class name, such as “CLSID_Account”. GUIDs for interfaces are of type IID,  and all start with “IID_” followed by the interface name such as “IID_IAccount”.

To convert between CLSID and unicode array:

 

CLSID clsid;

...

WCHAR * ostr;

HRESULT hr = StringFromCLSID(clsid, &ostr);

Program “Microsoft Visual Studio\Common\Tools\Guidgen.exe” is used to generate a guid. Choose the format of the guid and press button “Copy”. The generated guid will be in the clipboard.

You can also add it to the “Tools” menu with menu “Tools | Customize | Tools”.

1.4.        OLE/COM Object Viewer

OLE/COM Object Viewer goes through System Registry and collects all info about each coclass and their interfaces, and put them under one entry as their “user names”, which is the help string in the IDL file. You can find this entry under “All Objects” entry.

The “user name” entry stores the info about the coclass itself. Under it there are entries for all the interfaces of this class. Each entry stores the info about a specific interface.

OLD/COM Object Viewer can be started both from the “Start” menu and from Visual C++ menu “Tools”.

1.5.        System Registry

System Registry contains information about the computer. Under its “HKEY_CLASSES_ROOT” entry, all information about a coclass is stored. Under this entry, all coclasses are listed by their ProgIDs. Among the countless ProgID entries, there are two other entries: one named CLSID, under which all coclasses are listed by their guids; the other named Interface, under which all interfaces are listed by their guids.

ProgIDs are not guaranteed to be unique. They are used by some languages that can not directly refer to guids such as VBScript. VB can directly refer to guids, and can also optionally use ProgIDs. COM library provides to functions which can go through the System Registry and convert a ProgID to a CLSID or vice versa:

 

CLSID clsid;

CLSIDFromProgID(L"Cars.Jeep.1", &clsid);

 

LPOLESTR progid;

ProgIDFromCLSID(clsid, &progid);

We also need an entry to store some attributes about the whole server. For each server we can assign an AppID, under which we store all those atttributes such as AccessPermission, AuthenticationLevel, DllSurogate, LunchPermissions, RemoteServerName, etc. Then under each coclass entry under HKCR\CLSID, we add an AppID entry containing the AppID of the server.

When a coclass in invoked, the SCM reads the CLSID entry under HKCR\CLSID. If it finds an AppID entry, it will go further to find the AppID entry under HKCR\AppID. There it reads more about the server and knows how to deal with the server. For example, if SCM finds RemoteServerName entry there containing the name of another computer, it knows that the server is located oin another computer and it should contact the SCM of that computer.

When you call COM library functions such as CoCreateInstance passing a guid constant such as "CLSID_CoCar" or "IID_IRegistration", the compiler converts the guid contstant to the real guid by looking up the guid definition file *_i.c. At run time the real guid is sent to the API function.

¨    DLL server's entries in System Registry

A DLL server should have the following basic entries in the System Registry:

 

HKEY_CLASSES_ROOT\<ProgID>\CLSID = <clsid>

HKEY_CLASSES_ROOT\CLSID\<clsid> = <ProgID>

HKEY_CLASSES_ROOT\CLSID\<clsid>\InprocServer32 = <server full path>

You do not have to register your type library, for VC can directly #import from any directory, and VB and J++ can browse to find a *.tlb file. However, if you want to do it, you should add the following type library entries:

 

HKEY_CLASSES_ROOT\CLSID\<clsid>\TypeLib = <libid>

HKEY_CLASSES_ROOT\TypeLib\<libid> = <type library help string>

HKEY_CLASSES_ROOT\TypeLib\<libid>\1.0\0\Win32 = <type library full path>

HKEY_CLASSES_ROOT\TypeLib\<libid>\1.0\FLAGS = 0

HKEY_CLASSES_ROOT\TypeLib\<libid>\1.0\HELPDIR

When client calls CoCreateInstance with a CLSID and a IID, the COM run time will:

1.         go to the HKCR\CLSID\<guid>\InProcServer32 entry to find the location of the DLL server.

2.         load the DLL into process, call its exported DllGetClassObject function passing the coclass guid to get an IClassFactory pointer of the corresponding class factory;

3.         call the IClassFactory's CreateInstance method passing the IID of the interface to get a pointer of the interface.

You can see in the above process that the guids of the interfaces are only used inside the class factory. There is no need to store an entry for an interface in the System Registry.

¨    EXE server's entries in System Registry

A EXE server should have similar basic entries as a DLL server except it is LocalServer32:

 

HKEY_CLASSES_ROOT\<ProgID>\CLSID = <clsid>

HKEY_CLASSES_ROOT\CLSID\<clsid> = <ProgID>

HKEY_CLASSES_ROOT\CLSID\<clsid>\LocalServer32 = <server full path>

Besides these, it should have the following entries for each interface:

 

HKEY_CLASSES_ROOT\Interface\<iid>

HKEY_CLASSES_ROOT\Interface\ProxyStubClsid = <proxy/stub dll clsid>

HKEY_CLASSES_ROOT\Interface\ProxyStubClsid32 = <proxy/stub dll clsid>

If you use universal marshaller oleaut32.dll, which reads the registered type library and creates proxy/stub classes on the fly, you should put the CLSID of oleaut32.dll under ProxyStubClsid32, add the following interface entry, plus all the type library entries:

 

HKEY_CLASSES_ROOT\Interface\TypeLib = <type library libid>

For each interface you invoke, one proxy/stub object is instantiated. Therefore, you must set up a correspondence between an interface and its proxy/stub DLL server, so that when client invoke an interface, the SCM can find the corresponding proxy/stub DLL and create the corresponding proxy/stub object from it.

For this purpose, the System Registry stores the guids of the all interfaces of an EXE server under entry HKCR\Interface. Under each interface guid entry there is a ProxyStubClsid32 entry, which stores the CLSID of the custom proxy/stub DLL server which contains the proxy/stub coclass for that interface, or the universal marshaller oleaut32.dll.

Therefore, if you use the type library marshalling, you must register the type library under HKCR\TypeLib entry and each interface entry.

System Registry editor can be started at command line by typing “regedit”, or from OLE/COM Object Viewer in Visual C++.

 

2.      Unicode Handling

2.1.        Definitions of Different Type of Strings

¨    wchar_t

In C++ wchar_t is 16-bit wide character represented by unsigned short:

 

typedef wchar_t WCHAR;    // wc, 16-bit UNICODE character

typedef unsigned short wchar_t;

¨    LPOLESTR

In C++ LPOLESTR is a 16-bit unicode string represented by const unsigned short *:

 

typedef OLECHAR __RPC_FAR * LPOLESTR;

typedef WCHAR OLECHAR;

¨    LPCOLESTR

In C++ LPOLESTR is a constant 16-bit unicode string represented by const unsigned short *:

 

typedef const OLECHAR __RPC_FAR *LPCOLESTR;

¨    LPSTR

In C++ LPSTR is 8-bit multi-byte/ANSI string represented by char *

 

typedef CHAR *LPSTR, *PSTR;

typedef char CHAR;

¨    LPCSTR

In C++ LPCSTR is 8-bit multi-byte/ANSI string represented by const char *:

 

typedef CONST CHAR *LPCSTR, *PCSTR;

¨    TCHAR

In C++ TCHAR is 8-bit character represented by char:

 

typedef char TCHAR, *PTCHAR;

2.2.        CString <=> Multibyte String

 

#define m(x) ::MessageBox(NULL, x, "Test", MB_OK | MB_TOPMOST)

 

CString cstr("Hello the world");

 

    // CString::GetBuffer returns a pointer to its underlying buffer.

    LPSTR lpsz = cstr.GetBuffer(cstr.GetLength());

 

    // Both lpsz and cstr can access the underlying buffer.

m(lpsz);

m(cstr);

 

    // You can amend the buffer through cstr.

    cstr = "Hi Frank.";

m(lpsz);

 

    // You can also amend the buffer through lpsz, but you can't free it.

lpsz[8] = '!';

m(cstr);

    // free(lpsz); // Not allowed! Will cause run-time error!

 

    // After releasing the buffer, lpsz's content becomes undefined

cstr.ReleaseBuffer();

m(cstr);

 

    // If you want to avoid amending the original content of the CString,

    // make a copy.

    LPSTR lpszCopy = (LPSTR)malloc(strlen(lpsz));

memset(lpszCopy, 0, strlen(lpsz) + 1);

    strcpy(lpszCopy, lpsz);

m(lpszCopy);

2.3.        CString <=> Wide Character String

 

void CICPLoginDlg::CStringToWideChar(CString cstr, wchar_t * pwchar, int size)

{

    int cstrLen = cstr.GetLength();

ASSERT(cstrLen < MAX_LENGTH);

    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, cstr.GetBuffer(cstrLen),

cstrLen, pwchar, size);

cstr.ReleaseBuffer();

}

2.4.        Allocating and Freeing BSTRs with API Functions

BSTR stands for basic string. It is a 16-bit Unicode with a prefix telling the count of the bytes in this string, therefore do not need NULL at the end.

When a server creates and passes out a BSTR, the server allocates memory space for the BSTR, while the client should deallocate the memory with a call to API function SysFreeString.

¨    Creating a new BSTR

 

BSTR bstr1 = SysAllocString(L"Hello world!");

OLECHAR * pOleStr = OLESTR("Good morning!");

BSTR bstr2 = SysAllocString(pOleStr);

¨    Modifying a BSTR

 

SysReAllocString(&bstr1, L"Good morning!");

¨    Freeing a BSTR

 

SysFreeString(bstr1);

2.5.        BSTR <=> CString

 

CString cstr(“Hello!”);

BSTR bstr = cstr.AllocSysString();

 

BSTR bstr = SysAllocString(L"Hello Frank!");

CString cstr(bstr);

2.6.        BSTR <=> Multibyte String

Second, use function wcstombs (wide character string to multibyte string) and mbstowcs (multibyte string to wide character string):

 

BSTR bstr1 = SysAllocString(L"Hellow world!");

char * buf[80];

wcstombs(buf, bstr1, 80);

SysFreeString(bstr1);

 

sprintf(buf, "Good morning!");

BSTR bstr2;

mbstowcs(bstr2, buf, 80);

2.7.        Multibyte String <=> Wide Character String

 

char * mbstr = "Hello world!";

int len1 = strlen(mbstr);

 

wchar_t wcbuf[80];

int len2 = sizeof(wcbuf) / sizeof(wchar_t);

::memset(wcbuf, 0, sizeof(wcbuf));

 

MultiByteToWideChar(

    CP_ACP,

    MB_PRECOMPOSED,

    mbstr,

    len1,

    wcbuf,

    len2);

 

char buf[300];

sprintf(buf, "wcbuf = %S", wcbuf);

::MessageBox(NULL, buf, NULL, MB_OK);

 

 

wchar_t * wcstr = L"Hello world!";

int len3 = wcslen(wcstr);

 

char mbbuf[300];

int len4 = sizeof(mbbuf);

::memset(mbbuf, 0, len4);

 

WideCharToMultiByte(

    CP_ACP,

    WC_COMPOSITECHECK,

    wcstr,

    len3,

    mbbuf,

    len4,

    NULL,

    NULL);

 

::MessageBox(NULL, mbbuf, NULL, MB_OK);

2.8.        Converting with ATL Macros

ATL provides a group of macros to convert between different types. Because one convertion involves a series of temporary variables to hold and swap the string buffers, the space is prepared by macro USES_CONVERSION, which should be called before any conversion. They are defined in <atlconv.h>.

 

ANSI to ...

OLE to ...

TCHAR to ...

wchar_t to ...

A2BSTR

OLE2A

T2A

W2A

A2COLE

OLE2BSTR

T2BSTR

W2BSTR

A2CT

OLE2CA

T2CA

W2CA

A2CW

OLE2CT

T2COLE

W2COLE

A2OLE

OLE2CW

T2CW

W2CT

A2T

OLE2T

T2OLE

W2OLE

A2W

OLE2W

T2W

W2T

 

USES_CONVERSION;

MessageBox(NULL, W2A(bstr), NULL, MB_OK);

2.9.        MFC's BSTR Helper Class _bstr_t

MFC provides class _bstr_t to wrap BSTR. _bstr_t's constructor can take many types as input:

 

_bstr_t( ) throw( );

_bstr_t( const _bstr_t & s1 ) throw( );

_bstr_t( const char * s2 ) throw( _com_error );

_bstr_t( const wchar_t * s3 ) throw( _com_error );

_bstr_t( const _variant_t & var ) throw ( _com_error );

_bstr_t( BSTR bstr, bool fCopy ) throw ( _com_error );

Its assignment operator = is also overloaded for many types:

 

_bstr_t& operator=( const _bstr_t & s1 ) throw ( );

_bstr_t& operator=( const char * s2 ) throw( _com_error );

_bstr_t& operator=( const wchar_t * s3 ) throw( _com_error );

_bstr_t& operator=( const _variant_t & var ) throw( _com_error );

It has also overloaded +=, +, ! and all comparison operators.

To get a LPTSTR from _bstr_t:

 

  _bstr_t bstrt("Hello!");

  LPTSTR lp1 = (LPTSTR)bstrt;

  LPTSTR lp2 = bstrt.operator char *();

You can use _bstr_t anywhere expecting BSTR. If you like, you can also explicitly cast _bstr_t to BSTR:

 

_bstr_t bstrt("Hello!");

BSTR bstr = (BSTR)bstrt;

SysFreeString(bstr1);

_bstr_t object does not need to be deallocated. It is deallocated automatically when it leaves scope.

Class _bstr_t is contained in header file <comdef.h>.

2.10.     ATL's BSTR Helper Class CComBSTR

ATL also provides a wrapper class CComBSTR, which is more light than _bstr_t. CComBSTR wraps a BSTR data member m_str. Space for BSTR is allocated in its constructor and deallocated in the destructor.

Its constructor can take LPCOLESTR or LPCSTR as input. It can also designate the size of the buffer.

 

CComBSTR( int nSize );

CComBSTR( int nSize, LPCOLESTR sz );

CComBSTR( int nSize, LPCSTR sz );

CComBSTR( LPCOLESTR pSrc );

CComBSTR( LPCSTR pSrc );

CComBSTR( const CComBSTR& src );

Therefore you can say:

 

CComBSTR comBstr(“Hello!”);

To detach m_str from CComBSTR:

 

BSTR bstr = comBstr.Detach();

To attach a BSTR to a CComBSTR:

 

BSTR bstr = SysAllocString(L"Hello!");

CComBSTR comBstr;

comBstr.Attach(bstr);

To get a copy of m_str:

 

BSTR bstr = comBstr.Copy();

To get the address of m_str:

 

BSTR * pBstr = &comBstr.

To free m_str:

 

comBstr.Empty();

The overloaded assignment operator takes LPCOLESTR or LPCSTR as parameter. Therefore, to change the value of CComBSTR:

 

CComBSTR comBstr();

comBstr = L"Good morning!";

CComBSTR can only be casted to CString, which can be than casted to LPCSTR or LPCTSTR:

 

cstr = (CString)comBstr;

CComBSTR is contained in header file <atlbase.h>, which also contains other wrapper classes.

3.      DLL and EXE Server Without ATL

3.1.        Creating a DLL Server in Purely C++ Code

¨    Step 1:  create a housing for your DLL

Create a new source file with the name of the project, say CarDll.cpp, with two global variables:

 

//************* CarDll.cpp *******************

#include <windows.h>

 

ULONG g_lockCount = 0;

ULONG g_objCount = 0;

These two global variables are used for life time control of the DLL server (explained later). The <windows.h> header file is the one essential include file required in all Windows source code. It contains all of the definitions for Windows messages, constants, flag values, data structures, macros, and other mnemonics that permit the programmer to work without having to memorize thousands of hexadecimal values and their functions.

¨    Step 2:  define your interfaces

Create a header file in any name in an empty DLL project workspace, say "interface.h", and define your interfaces in it:

 

//*************** interface.h ***********************

#ifndef _INTERFACES

#define _INTERFACES

 

#include <unknwn.h> // Needed if WIN32_LEAN_AND_MEAN is defined in stdafx.h

#include <windows.h>

 

struct IRegistration : public IUnknown

{

    virtual HRESULT __stdcall GetOwner(BSTR * pBstrOwner) = 0;

virtual HRESULT __stdcall SetOwner(BSTR bstrOwner) = 0;

};

 

struct IStatus : public IUnknown

{

virtual HRESULT __stdcall GetSpeed)(int * pnSpeed) = 0;

virtual HRESULT __stdcall SetSpeed)(int nSpeed) = 0;

};

 

#endif

Calling convention __stdcall defines the way function call arguments are pushed into stack, poped out, and name-decorating convention that the compiler uses to identify individual functions.

¨    Step 3:  define guids for interfaces

Create a new header file in any name, say iid.h. Use guid generator guidgen.exe to directly generate new guids in correct format for all new interfaces, and assign a constant alias for each guid:

 

//**************** iid.h *********************

// {D427CA52-AF28-40a4-A5C2-97EA029DCD0F}

DEFINE_GUID(IID_IRegistration,

0xd427ca52, 0xaf28, 0x40a4, 0xa5, 0xc2, 0x97, 0xea, 0x2, 0x9d, 0xcd, 0xf);

 

// {D518B0BF-3EE1-4976-9B6A-9F3443A2A186}

DEFINE_GUID(IID_IStatus,

0xd518b0bf, 0x3ee1, 0x4976, 0x9b, 0x6a, 0x9f, 0x34, 0x43, 0xa2, 0xa1, 0x86);

 

// {2F481E63-C189-4d99-A705-9F3F2DFB7145}

DEFINE_GUID(CLSID_Car,

0x2f481e63, 0xc189, 0x4d99, 0xa7, 0x5, 0x9f, 0x3f, 0x2d, 0xfb, 0x71, 0x45);

Create a corresponding source file iid.cpp. Simply include the following lines in the file:

 

//***************** iid.cpp *********************

#include "stdafx.h"

#include <windows.h>

#include <objbase.h> // Needed if WIN32_LEAN_AND_MEAN defined in stdafx.h

#include <initguid.h> // contains definition of DEFINE_GUID

#include "iid.h"

Header file <initguid.h> should be included before "iid.h". By putting the lines shown above into a cpp file and letting the client programming inserting this cpp file into his project, this precedence is guaranteed.

Note that when a client invokes for an IID or CLSID from the COM run time, the program will first loop up the guid definition file (iid.h in this case) to acquire the actual guid. That is to say, it is "0xd427ca52, 0xaf28, 0x40a4, 0xa5, 0xc2, 0x97, 0xea, 0x2, 0x9d, 0xcd, 0xf" not "IID_IRegistration" which is passed to the COM run time to be used to search in the System Registry. This is how the guid helps to maintain the global uniquenss of a coclass or interface.

¨    Step 4:  create the coclass which implements the interfaces

 

//**************** Car.h **********************

#include "interfaces.h"

 

class Car : public IRegistration, public IStatus {

public:

Car();

virtual ~Car();

 

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

STDMETHODIMP_(ULONG) AddRef();

STDMETHODIMP_(ULONG) Release();

 

STDMETHODIMP GetOwner(BSTR * pBstrOwner);

STDMETHODIMP SetOwner(BSTR bstrOwner);

 

STDMETHODIMP GetSpeed(int * pnSpeed);

STDMETHODIMP SetSpeed(int nSpeed);

 

private:

    ULONG m_refCount;

 

char m_pcOwner[80];

    int m_nSpeed;

};

 

//****************** Car.cpp ************************

#include "Car.h"

#include "iid.h"

 

extern ULONG g_lockCount;

extern ULONG g_objCount;

 

Car::Car()

{

    m_refCount = 0;

    g_objCount++;

::memset(m_pcOwner, 0, 80);

}

 

Car::~Car()

{

    g_objCount--;

}

 

STDMETHODIMP_(ULONG) Car::AddRef()

{

return ++m_refCount;

}

 

STDMETHODIMP_(ULONG) Car::Release()

{

    if(--m_refCount == 0)

    {

delete this;

return 0;

    }

else

return m_refCount;

}

 

STDMETHODIMP Car::QueryInterface(REFIID riid, void ** ppAny)

{

    // IID_IUnknown is the REFIID of standard interface IUnknown

    if(riid == IID_IUnknown)   

    {

// to avoid confusion caused by virtual inheritance

*ppAny = (IUnknown *)(IStatus *)this;

    }

    else if(riid == IID_IRegistration)

    {

*ppAny = (IRegistration *)this;

    }

    else if(riid == IID_IStatus)

    {

*ppAny = (IStatus *)this;

    }

else

    {

*ppAny = NULL;

return E_NOINTERFACE;

    }

 

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

    return S_OK;

}

 

STDMETHODIMP Car::GetOwner(BSTR * pBstrOwner)

{

mbstowcs(*pBstrOwner, m_pcOwner, 80);

return S_OK;

}

 

STDMETHODIMP Car::SetOwner(BSTR bstrOwner)

{

wcstombs(m_pcOwner, bstrOwner, 80);

return S_OK;

}

 

STDMETHODIMP Car::GetSpeed(int * pnSpeed)

{

*pnSpeed = m_nSpeed;

return S_OK;

}

 

STDMETHODIMP Car::SetSpeed(int nSpeed)

{

m_nSpeed = nSpeed;

return S_OK;

}

Macro STDMETHODIMP and STDMETHODIMP_(Type) are used to convert the function into __stdcall calling convention:

 

#define STDMETHODIMP            HRESULT STDMETHODCALLTYPE

#define STDMETHODIMP_(type)     type STDMETHODCALLTYPE

Pay attention the cascaded if structure in the QueryInterface method. In iid.h, we stored some guids and assigned an alias for each of them. But actually nothing linked one guid/ alias to one interface.  Now here we find the link.

¨    Step 5:  Create one class factory for each coclass

In a separate header and source file, create a corresponding class factory class for each coclass. When its CreateInstance method is called, it simply create an instance of the corresponding coclass, and query its QueryInterface method for the passed REFIID.

This is where a class factory is matched to its corresponding coclass.

 

//***************** CarClassFactory.h ***********************

#ifndef _CARCLASSFACTORY

#define _CARCLASSFACTORY

 

#include <windows.h>

 

class CarClassFactory : public IClassFactory {

public:

CarClassFactory();

    virtual ~CarClassFactory();

 

HRESULT __stdcall QueryInterface(REFIID riid, void ** ppAny);

ULONG __stdcall AddRef();

ULONG __stdcall Release();

 

    HRESULT __stdcall CreateInstance

(LPUNKNOWN pUnkOuter, REFIID riid, void ** ppAny);

    HRESULT __stdcall LockServer(BOOL fLock);

 

private:

ULONG m_refCount;

};

 

#endif

 

 

//******************** CarClassFactory.cpp ***********************

#include "CarClassFactory.h"

#include "car.h"

 

extern ULONG g_lockCount;

extern ULONG g_objCount;

 

CarClassFactory::CarClassFactory()

{

    m_refCount = 0;

    g_objCount++;

}

 

CarClassFactory::~CarClassFactory()

{

    g_objCount--;

}

 

ULONG __stdcall CarClassFactory::AddRef()

{

return ++m_refCount;

}

 

ULONG __stdcall CarClassFactory::Release()

{

    if(--m_refCount == 0)

    {

delete this;

return 0;

    }

 

return m_refCount;

}

 

HRESULT __stdcall CarClassFactory::QueryInterface(REFIID riid, void ** ppAny)

{

    // IID_IUnknown is the REFIID of standard interface IUnknown

if(riid == IID_IUnknown)

    {

*ppAny = (IUnknown *)this;

    }

else if(riid == IID_IClassFactory)

    {

*ppAny = (IClassFactory *)this;

    }

else

    {

*ppAny = NULL;

return E_NOINTERFACE;

    }

 

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

return S_OK;

}

 

HRESULT __stdcall CarClassFactory::CreateInstance

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

{

if(pUnkOuter != NULL)

    {

return CLASS_E_NOAGGREGATION;

    }

 

    Car * pCar = new Car();

    HRESULT hr = pCar->QueryInterface(riid, ppAny);

if(FAILED(hr)) delete pCar;

return hr;

}

 

HRESULT __stdcall CarClassFactory::LockServer(BOOL fLock)

{

if(fLock)

        g_lockCount++;

else

g_lockCount--;

 

return S_OK;

}

Note that for this class factory class I did not use STDMETHOD macros, instead I directly used __stdcall.

¨    Step 6:  implement exported functions

The mission of the exported function DllGetClassObject is to hand to outside client the IClassFactory pointer which is implemented by the class factory. It takes the REFCLSID of a coclass, create an instance of its corresponding class factory and query its QueryInterface for IClassFactory interface pointer.

This is where the REFCLSID of the coclass is matched to its corresponding class factory.

 

#include "CarClassFactory.h"

#include "iid.h"

 

ULONG g_lockCount = 0;

ULONG g_objCount = 0;

 

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void ** ppAny)

{

if(rclsid != CLSID_Car)

return CLASS_E_CLASSNOTAVAILABLE;

 

    CarClassFactory * pFactory = new CarClassFactory;

HRESULT hr = pFactory->QueryInterface(riid, ppAny);

if(FAILED(hr))

delete pFactory;

 

return hr;

}

 

STDAPI DllCanUnloadNow()

{

if(g_lockCount == 0 && g_objCount == 0)

        return S_OK;

else

return S_FALSE;

}

¨    Step 7:  export the Dll functions

Create a new def file (under source code) with the same name as the project workspace, which is CarDll.def to export the Dll functions:

 

LIBRARY "CarDll"

EXPORTS

  DllGetClassObject     @1 PRIVATE

  DllCanUnloadNow       @2 PRIVATE

¨    Step 8:  provide registration information

Create a new reg file say CarDll.reg to merge the server information such as the GUIDs and the location into the System Registry:

 

REGEDIT

HKEY_CLASSES_ROOT\CarDll.Car\CLSID = {2F481E63-C189-4d99-A705-9F3F2DFB7145}

HKEY_CLASSES_ROOT\CLSID\{2F481E63-C189-4d99-A705-9F3F2DFB7145} = CarDll.Car

HKEY_CLASSES_ROOT\CLSID\{2F481E63-C189-4d99-A705-9F3F2DFB7145}\InprocServer32 = D:\My Documents\Visual C++\CarDLL\debug\CarDll.dll

Note that each line is a "key = value". There should be a space on each side of the equal sign, and no space (including tab) elsewhere.

Double-click this reg file to merge the server information into the System Registry.

3.2.        Life Time Control of a Coclass in a DLL Server

As we can see from the above example, a COM object is initiated by a client through the exported function DllGetClassObject, but is deleted by itself. This is the same with class factories in a DLL.

Each coclass has a ULONG member m_refCount. In both the above raw C++ code and ATL’s implementation, each time QueryInterface successfully hands out an interface pointer, it increments the coclass reference count by calling AddRef. Therefore, after you acqure an interface pointer with CoCreateInstance, you do not need to call AddRef. However, after you make a copy of the interface pointer, you should call AddRef. A function receiving an interface pointer as parameter is also making copy of the pointer and thus should also call AddRef.

Each time you (as a client or a function which receives an interface pointer as parameter) finishes using an interface, you should call its Release method. The coclass implementation of Release will decrement m_refCount, then check whether it reaches zero. If yes, it will delete itself.

The correct deletion of a COM object depends on the correct operation of the client. If one client calls AddRef or Release incorrectly, the COM object may be in memory when it should be deleted, or deleted when some one is still using it. Especially, if you call any method after the last Release call is made, you are stepping into OS’s territory and your program will be shut down.

3.3.        Life Time Control of a DLL Server

A DLL server is both loaded and unloaded by the COM rum time. When a client calls DllGetClassObject with the REFCLSID of a coclass in the server, SCM will look up the System Registry, find the directory of the server, load the server up, then call its exported function DllGetClassObject.

A DLL server has two global ULONG counts affecting its life time control: g_objCount and g_lockCount. The former one indicates the number of live COM objects in the server and the latter one the number of locks imposed by clients on the server.

The constructor of a coclass and a class factory in a DLL is supposed to increment the g_objCount. Its destructor is supposed to decrement g_objCount. On the other hand, for performance reason, a client may decide to to load the server into memory first, and create COM objects later. To lock the server, he calls its LockServer method passing TRUE, which will then increment the g_lockCount. To release the lock, the client calls the same method passing FALSE.

Only when both of these two locks reaches zero, will the DLL’s exported function DllCanUnloadNow return true. SCM will call this function at intervals to check whether the server can be unloaded. If yes it will unload the DLL from the memory. If a client wants the SCM to check this function and unload the server immediately if possible, it can call API function CoFreeUnusedLibraries.

Because DLL class factories also have to delete themselves, the server can not be unloaded before all objects including class objects are done. That’s why in DLL server class factories also affect the server reference counting.

3.4.        EXE Server in Raw C++ Code

Because coclasses in EXE server are the same as those in DLL server, and class factories has only one difference that it does not affect any reference count, their code is omitted here:

 

DWORD g_allLocks;

 

int APIENTRY WinMain(HINSTANCE hInstance,

                     HINSTANCE hPrevInstance,

                     LPSTR     lpCmdLine,

                     int       nCmdShow)

{

    CoInitialize(NULL);    

 

    // register the type lib

ITypeLib* pTLib = NULL;

LoadTypeLibEx(L"AnyEXETypeInfo.tlb", REGKIND_REGISTER, &pTLib);

pTLib->Release();

   

if(strstr(lpCmdLine, "/Embedding") || strstr(lpCmdLine, "-Embedding"))

    {

        AnyClassFactory cf; // class factories created as local objects

 

DWORD regID = 0;

CoRegisterClassObject(CLSID_Any,

(IClassFactory*)&cf,

CLSCTX_LOCAL_SERVER,

REGCLS_MULTIPLEUSE,

                              &regID);

MSG ms;

while(GetMessage(&ms, 0, 0, 0))

{

TranslateMessage(&ms);

DispatchMessage(&ms);

}

CoRevokeClassObject(regID);   

    }

 

CoUninitialize();   

return 0;

}

 

void Lock()

{

    ++g_allLocks;

}

 

void UnLock()

{

--g_allLocks;

if(g_allLocks == 0)

        PostQuitMessage(0);

}

 

3.5.        Life Time Control of a Coclass and Class Factory in an EXE Server

Coclasses in an EXE server works exactly like those in a DLL server.

Class objects in an EXE server has one difference from those in a DLL server: they don't affect any reference count – neither their own nor the server’s. This is because they are created in WinMain as local objects on the stack, so they do not need to be explicitly deleted. When all coclass objects are done, WinMain will end, and when WinMain ends, all class objects go out of scope.

So EXE class factories use dummy implementation for AddRef and Release: they simply returns constants like 10 or 20. The rest of the class factory are the same like those in a DLL server.

3.6.        Life Tme Control of an EXE Server

A DLL server can not be run independently. It is started by the client’s thread. It only has some exported functions such as DllGetClassObject for clients to call. It can not unload itself. In comparison, an EXE server has its main thread. When an EXE is started, the main thread runs global function WinMain. When it reaches its end bracket, the whole server process ends just like normal applications.

Unlike a DLL server which uses two global counts, an EXE server has only one global count g_allLocks for life time control.

Whenever a coclass is instantiated, its constructor will call global function Lock, which will increase the lock count. Whenever it is destroyed, its destructor will call a global UnLock, which will decrease the lock, then check whether it is zero. If yes, post an “exit” message to end the dummy message look. Class factory's LockServer method also calls Lock and UnLock. So the global variable UnLock controls the EXE server life time.

Class factory's constructor and destructor doesn't call Lock and Unlock.

3.7.        Nomal EXE Application vs. EXE Server

An EXE server can be designed as purely a server, or it can be designed to be able to server both as a server which is loaded as terminated by the COM run time, and an application with user interface which is started and ended by the user. How the program is run is decided by the command-line argument “Embedding”.

When COM run time starts the EXE as a server, it will invoke it with argument “Embedding”. So WinMain knows that it is launched as a server. It will create the class factory objects for all the coclasses the server contains, register each of them in the Running Object Table by calling CoRegisterClassObject, then enter a dummy message loop and wait infinitely for an “Exit” message. When the server's global Unlock function discovers that the server lock reaches zero, it will post an “Exit” message to end the message loop. Then function CoRevokeClassObject will be called for each class object. Then WinMain ends.

When user starts the EXE as an application, he will not put argument “Embedding”. So WinMain knows that it is launched as an application. Instead of doing the above things, it will create and show its user interfaces.

3.8.        Example of EXE Server's WinMain

To register and unregister a class object CoCarClassFactory to the running object table:

 

DWORD g_allLocks = 0;

 

int APIENTRY WinMain(HINSTANCE hInstance,

                     HINSTANCE hPrevInstance,

                     LPSTR     lpCmdLine,

                     int       nCmdShow)

{

    CoInitialize(NULL);    

 

    if(strstr(lpCmdLine, "/Embedding") || strstr(lpCmdLine, "-Embedding"))

    {

        CoCarClassFactory carClassFactory;

        DWORD regID = 0;

        CoRegisterClassObject(CLSID_CoCar,

(IClassFactory*)&carClassFactory,

CLSCTX_LOCAL_SERVER,

REGCLS_MULTIPLEUSE, &regID);

        MSG ms;

        while(GetMessage(&ms, 0, 0, 0))

        {

            TranslateMessage(&ms);

            DispatchMessage(&ms);

        }

        CoRevokeClassObject(regID);   

    }

    else

    {

        // Work as a normal application

    }

 

    CoUninitialize();   

    return 0;

}

4.      Client Invoking Server

4.1.        Accessing DLL and EXE Servers using Class Factories

There are two steps to access a server (DLL server or EXE server alike) through class factories:

1.       Client calls CoGetClassObject providing the REFCLSID of the coclass, which searches the System Registry, loads up the server if it is not yet loaded, calls the server's exported function DllGetClassObject, which returns its IClassFactory pointer;

2.       Client calls IClassFactory::CreateInstance with the REFIID of an interface of the coclass, which returns the interface pointer;

COM library API function CoGetClassObject calls the COM server’s DllGetClassObject to acquire a class factory reference:

 

HRESULT CoGetClassObject(

REFCLSID rclsid,

    DWORD dwClsContext,

COSERVERINFO * pServerInfo,

REFIID riid,

LPVOID * ppv);

COSERVERINFO is a structure to carry information for remote servers, which should be NULL if you are invoking in-proc or local servers.

Then you can acquire from the IClassFactory interface the interface you want.

Example:

Use AppWizard's option "Win32 Console Application" and "Empty Project" to create a totally empty project. Insert the iid.cpp file into the project, and put the following lines into the global file "ProjectName.cpp":

 

#include "..\cardll\interfaces.h"

#include "..\cardll\iid.h"

#include <iostream.h>

// Note: interface definition "interface.h" should be included prior to

// GUID definition "iid.h"

 

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

{

    CoInitialize(NULL);

IClassFactory * pClassFactory = NULL;

HRESULT hr = CoGetClassObject(

CLSID_Car,

CLSCTX_SERVER,

NULL,

IID_IClassFactory,

(void **) &pClassFactory);

if(FAILED(hr)) // in the following code this error checking is omitted

    {

cout << "CoGetClassObject failed!\n";

return;

    }

 

IRegistration * pRegist = NULL;

    hr = pClassFactory->CreateInstance(

        NULL,

IID_IRegistration,

(void **) &pRegist);

 

    BSTR bstr = SysAllocString(L"Frank Liu");

    hr = pRegist->SetOwner(bstr);

SysFreeString(bstr);

 

    hr = pRegist->GetOwner(&bstr);

char buf[80];

wcstombs(buf, bstr, 80);

cout << "Owner of the car is: " << buf << endl;

 

IStatus * pStatus = NULL;

pRegist->QueryInterface(IID_IStatus, (void **)&pStatus);

 

    hr = pStatus->SetSpeed(120);

    int nSpeed;

    hr = pStatus->GetSpeed(&nSpeed);

cout << "Speed of the car is now " << nSpeed << endl;

 

return;

}

4.2.        Accessing Server with CoCreateInstance

COM library API function CoCreateInstance wraps the two function calls – CoGetClassObject and IClassFactory::CreateInterface:

 

#include "stdafx.h"

 

// The following two header files are generated by MIDL compiler.

// Alternatively, you can import the type library as instructed in

// Chapter IDL.

#include "..\test\test.h"

#include "..\test\test_i.c"

 

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

{

  CoInitialize(NULL);

  IAny * pAny = NULL;

 

  HRESULT hr = CoCreateInstance(

CLSID_Any,

NULL,

CLSCTX_INPROC_SERVER,

IID_IAny,

(void **)&pAny);

 

  if(FAILED(hr))

      printf("CoCreateInstance failed!");

  hr = pAny->Hi(); 

  pAny->Release();

  return 0;

}

4.3.        Execution Context

There are four execution contexts you can use when calling CoCreateInstance or CoCreateInstanceEx:

 

CLSCTX_INPROC_SERVER

CLSCTX_INPROC_HANDLER

CLSCTX_LOCAL_SERVER

CLSCTX_REMOTE_SERVER

CLSCTX_SERVER is an OR of the three server contexts.

5.      IDL

5.1.        Interface Definition Language (IDL)

With a server created purely in C++, without using IDL compiler,  a client have to make use of the two header files which defines the interfaces and the guids. Because both these two files are in C++ syntax, clients written in other languages such as VB or Java can not invoke it. So such a COM server is not language independent.

Therefore we need a language-independent way to describe the interfaces of a COM server. Microsoft IDL (MIDL) is used for this purpose.

An IDL file does not contain any implementation information. Therefore you can not define a class in IDL with implemented methods. You can only define interfaces in IDL and implement them in a specific language such as VC or VB. However, you can define data structures which doesn't contain any implementation.

5.2.        Type Library

When you define a COM server in an IDL file say Any.idl and send it to VC's integrated MIDL compiler, it will generate a C++ header file Any.h (which serves as interface definition), and a guid definition file Any_i.c for C++ clients, and a type library file Any.tlb containing all information of a COM server, which is in a language-independent binary format, therefore can be understood by different languages such as VC, VB, BJ++, etc.

When a C++ client wants to make use of type library, he can use #import to import the type library. When a VB client wants to invoke a COM server and design time, he can select from the list of registered type libraries from the "Project" | "Preferences" dialog. If one type library is not registered, he can also browse to select one.

To register a type library, under entry HKEY_CLASSES_ROOT\TypeLib, you should create an entry of the guid of the type library, under which you specify the help string and directory of this type library. This is why VB can show you a list of registered type libraries. You should add a TypeLib entry under the HKEY_CLASSES_ROOT\CLSID of each coclasses contained in this type library, so that SCM can find the type library guid from a coclass. All these things can be done by ATL.

In C++, binary information defined by IDL is stored in both the .dll file and the .tlb file. In Visual Basic it is only stored in DLL file. A EXE server will have its info in the EXE file.

5.3.        IDL Compatible Data Types

If you only use the following IDL data types, your interfaces can be used by all COM-enabled interfaces:

 

VARIANT_BOOL, double, float, long, short, BSTR, DATE, IDispatch*, IUnknown*, VARIANT, CY/CURRENCY, SAFEARRAY

However, when you use other types such as ULONG or HRESULT in your IDL, they are also OK, because they have been defined by typedef in "wtypes.idl" as IDL data types. You do not need to import "wtypes.idl", because it will be imported by "oaidl.idl".

5.4.        Defining Coclasses, Interfaces and Libraries in IDL

IDL file of an empty COM server looks like

 

import "oaidl.idl";

 

[

    uuid(7013DBBD-A9FC-11D5-9866-E39F283C9930),

    version(1.0),

    helpstring("Test 1.0 Type Library")

]

library TESTLib

{

importlib("stdole32.tlb");

};

"oaidl.idl" is the only IDL file you must import in your IDL file. Type library "stdole32.tlb" is the standard OLE type library, which must be imported by any type library.

¨    Adding a coclass

To add a new coclass, add the following coclass declaration lines in the library block:

 

  [

      uuid(7013DBCD-A9FC-11D5-9866-E39F283C9930),

      helpstring("MyCom Class")

  ]

  coclass MyCom

  {

  };

¨    Adding an interface and its methods

We can use AppWizard to add coclasses and their default interfaces, but we have to add additional interfaces ourselves. For each interface, we add its declaration in front of the library block. Attribute "object" indicates that this is a COM interface not a DCE IDL interface.

 

  [

      object,

      uuid(7013DBCB-A9FC-11D5-9866-E39F283C9930),

      helpstring("ICOMClass1 Interface")

  ]

  interface IMyInterf : IUnknown

  {

      [helpstring("method GetBalance")]

          HRESULT GetBalance([in] int aa, [out] LONG * bb);

      [helpstring("method Deposit")]

         HRESULT Deposit([in] int aa);

  };

Then we add the interface name in the coclass definition:

 

  [

      uuid(7013DBCD-A9FC-11D5-9866-E39F283C9930),

      helpstring("MyCom Class")

  ]

  coclass MyCom

  {

      [default] interface IMyInterf;

  };

the interface with [default] attribute is the default interface of the coclass.

An interface should belong to a coclass. A standard-alone interface which does not belong to any coclass is alowed by MIDL compiler and can be implemented manually, but it is not shown when using Implement Interface Wizard.

Then we should let the coclass inherit from the interface, and in ATL we should add a COM map entry for the interface.

5.5.        Defining Data Structures in IDL

If your server needs to use a custom type (a class or a structure) internally, you can define the new type in a normal header file (plus cpp file if necessary) and #include the header file wherever it is used.

However, if this custom type is used as an argument in a method of an interface which needs to be transfered by the marshaller, then you should define the custom type in your main IDL file, or in a separate IDL file and import it in your main IDL file.

Then, when MIDL compiler compiles the main IDL file, the header file it generates will contain the definition of the custom type. If the custom type is defined in a separate IDL file, then you have to compile the separate IDL file first, then the main IDL file. Then the header file generated from the main IDL file will automatically include the header file generated from the separate IDL file.

Because the main IDL file either contains the definition of the custom type or imports its definition, the client who invokes this server can directly use the custom type.

If the custom type contains any variable-length array member, you must put size_is attribute in front of the variable-length data member to inform the marshaller of the member's size:

 

typedef struct {

short sSize;

    [size_is(sSize)] short sArray[];

} Group;

Also notice that the array member is represented by array syntax "short sArray[ ]" not pointer syntax "short * pShort". For some reason the latter one doesn't work.

5.6.        Defining Enums in IDL

 

[uuid(442F....804), v1-enum]

typedef enum {

HATCH = 0,

SOLID = 1,

POLKADOT

} FILLTYPE;

Attribute v1-enum indicates that these enums are 32-bit, which is more efficient in marshalling.

5.7.        MkTypLib Compatible Option

Each VC project has a "MkTypLib Compatible" option. This option is found under "Project | Settings | MIDL". This is by default set, which means that all IDL files must conform to older ODL syntax. You should turn it off. In a project generated by ATL COM AppWizard, it is turned off.

5.8.        MIDL Generated Files

Suppose the IDL file name is Shapes.idl, the files generated by MIDL are:

1.        Shapes.h                                Interface definition

2.        Shapes.tlb                               binary type library

3.        Shapes_i.c                              definition of all guids

4.        Shapes_p.c and dlldate.c         used to build proxy and stub DLLs for remote accessing

5.9.        Importing Type Library

In C++, to invoke a server or implement an interface defined in it through its type library is to #import the type library wherever you need it – in the *.h or *.cpp file. When you #import a type library say Shapes.tlb, the compiler will generate two C++ files: Shapes.tlh and Shapes.tli, which contains definitions for the interfaces, guids and and smart pointers. Both files are placed in the output directory e.g. "debug", and then read and compiled by the compiler as if they are included. So after you compile the project, you can actually comment out the #import line and #include the tlh file.

A typical #import line looks like:

 

#import "D:\icp97\pc\core\code\ICPCore.tlb" \

    no_namespace, named_guids, raw_native_types, raw_interfaces_only

¨    no_namespace

If you do not put the "no_namespace" attribute after the #import, most sections in the generated tlh file will be enclosed in a namespace block, which is named after the type library name. This is useful to prevent name conflicts between names used in your client program and names imported from the COM server. To use the names in the COM server, you should put the following declaration immediately after the #import line (the name of the type library is ICPCore.tlb):

 

using namespace ICPCore;

By putting the "no_namespace" attribute, the namespace declaration in the generated tlh file will be supressed. Therefore all names in the tlh file are in the same namespace as your client program. You can directly quote them.

¨    named_guids

Attribute "named_guids" instructs the compiler to define the guids contained in the tlh file with CLSID_, IID_ and LIB_ prefix.

If a type library includes references to types defined in other type libraries, then the .TLH file will include comments of the following sort:

 

// Cross-referenced type libraries:

//

// #import "c:\path\typelib0.tlb"

¨    raw_native_types

By default, the high-level error-handling methods use the COM support classes _bstr_t and _variant_t in place of the BSTR and VARIANT data types and raw COM interface pointers. These classes encapsulate the details of allocating and deallocating memory storage for these data types, and greatly simplify type casting and conversion operations.

This attribute is used to disable the use of these COM support classes in the high-level wrapper functions, and force the use of low-level data types instead.

¨    raw_interfaces_only

This attribute suppresses the generation of error-handling wrapper functions and __declspec(property) declarations that use those wrapper functions.

It also causes the default prefix used in naming the non-property functions to be removed. Normally, the prefix is raw_.  E.g., if a method of the original interface is called MyMethod in the IDL and type library, then in the *.tlh file generated by the #import from the type library, it will become raw_MyMethod. If this attribute is specified, the function names are directly from the type library.

This attribute allows you to expose only the low-level contents of the type library.

5.10.     Smart Pointers

When VC compiler sees preprocessor directive #import *.tlb, it reads the type definition in the type library and generates smart pointer classes from IDL files, to wrap low-level functions calls such as CoCreateInstance, Release, InterfaceSupportsErrorInfo, GetErrorInfo, GetDescription, etc.. For interface IAccount and IDisplay, there will be smart pointer class IAccountPtr and IDisplayPtr.

These smart pointers can automatically acquire interface reference with CoCreateInstance. You do not need to call QueryInterface to acquire a new interface from the original one. You can just assign the original interface smart pointer to the new one. AddRef and Release are also called automatically.

When a when a smart pointer detects an error from the returned HRESULT, it will throw a _com_error exception. You can call its ErrorMessage method, which calls API function FormatMessage to return an explanation of the HRESULT, such as "Invalid pointer". To retrieve the extra error information set with IErrorInfo objects, call _com_error's Description method.

A smart pointer also supports IDL attribute [out, retval].

Because smart pointer classes are higher level than raw interface pointers, it is more error prone. I have once run into a job which can be done by using raw interface pointers but not smart pointer classes.

To use smart pointer for interface IAccount and IDisplay of coclass Account in server BankATL:

 

try{

    IAccountPtr pAccount("BankATL.Account.1");

    /* or you can say

IAccountPtr pAccount;

pAccount.CreateInstance(CLSID_Account);

or

IAccount pAccount(__uuidof(CAccount));

    */

 

    int balance;

pAccount->GetBalance(&balance);

pAccount->Deposit(23);

pAccount->GetBalance(&balance);

 

    IDisplayPtr pDisplay = pAccount;

pDisplay->Show();

}

catch(_com_error &ex)

{

MessageBox(ex.ErrorMessage());

}

When you pass the ProgID to a smart pointer's constructor, a CoCreateInstance call has already been done. Therefore, you should be cautious when you make a smart pointer a data member or especially a global variable – AfxOleInit must be called before the smart pointer is created.

5.11.      [retval] Attribute

Parameter marked with "[ out, retval ]" can be used by smart pointers or other languages such as VB as logical return type. Suppose method Add of interface IMath implemented by coclass CMath is defined as

 

HRESULT Add([in] long x, [in] long y, [out, retval] long * z);

You can say

 

IMathPtr sp (__uuidof(CMath));

long result = sp->Add(111, 222);

/* which is equal to

long result;

sp->Add(111, 222, &result);

*/

In VB:

 

Dim c as New CMath

Dim ans as Integer

ans = c.Add(111, 222)

5.12.     [oleautomation] Compatible Data Types

Originally COM specifications was generated to be used only for C/C++. Some of C/C++’s types doesn’t map to other languages such as VB. If your COM component has an interface method with such a data type as parameter, other languages may not be able to call this method.

To solve this problem, Microsoft developed a set of universal IDL data types that can map to all languages. It is based on VB’s data type “Variant”. They are:

 

Universal IDL Type

VB Mapping

J++ Mapping

C/C++ Mapping

VARIANT_BOOL

Boolean

boolean

VARIANT_BOOL

double

Double

double

double

float

Single

float

float

long

Long

long

long

short

Integer

short

short

BSTR

String

java.lang.String

BSTR

DATE

Date

double

DATE

IDispatch *

Object

java.lang.Object

IDispatch *

IUnknown *

Interface reference

com.ms.com.IUnknown

IUnknown *

VARIANT

Variant

com.ms.com.Variant

VARIANT

CY/CURRENCY

Currency

long

CY/CURRENCY

SAFEARRAY

Variant

com.ms.com.SafeArray

SAFEARRAY

 

If your COM component’s interface methods all use the above universal data types, then the component can be invoked by client programs written in all languages.

In such a case, you’d better put attribute [oleautomation] in the interface declaration:

 

  [

      object,

      uuid(C39A9A9A-1E04-11D6-9867-F05CFEE1FC30),

      oleautomation,

      helpstring("IAny Interface"),

      pointer_default(unique)

  ]

  interface IAny : IUnknown

  {

      [helpstring("method Hi")] HRESULT Hi([in] A a);

  };

With this attribute, MIDL compiler will check that all interface methods use only universal data types. If not, e.g. a method has a parameter of a custom type say CEmployee * pEmployee, it will generate a warning message like “interface does not conform to [oleautomation] attribute”.

If you use dispinterface or dual interface than you have to stick to this standard.

If your COM component comply to the [oleautomation] standard, it can make use of COM’s automatic data marshalling DLL oleaut32.dll. Otherwise you have to build and register your own stub/proxy DLL server (MIDL will generate the make file for you so it is not difficult either).

6.      Data Marshalling

6.1.        When is Data Marshaller Needed

When designing a DLL server, you can simply use a pointer to pass into the server an array, just like a normal method call, because the DLL server is in-process and shares the same address space:

 

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

HRESULT PassShorts([in] short count, [in] short * pShort);

 

STDMETHODIMP CAny::PassShorts(short * pShort, int count)

{

char temp[80];

 

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

    {

sprintf(temp, "%d \n", pShort[i]);

MessageBox(NULL, temp, "Server", MB_OK);

    }

 

return S_OK;

}

When it comes to EXE server, if you use the same code shown above, only pShort[0] has a legal value. If you try to access the succeeding array elements, the server will fail.

This is because the server and the client are not in the same address space, the data transfer between the client and the server is taken care of by the marshaller. When the client passes a pointer to the server or vice versa, the receiver actually does not get a pointer pointing to the original location in the sender's address space. Instead it gets a pointer to a copy of data in his own address space allocated by the marshaller. The marshaller transfers the data from the sender to the receiver.

For a in-process method call, both the caller and the method are refering to the same piece of physical data (in case of pass-by-pointer), the reference itself doesn't cause data flow. For a method call between a client and EXE server, however, a data reference itself will cause data flow, and therefore you must tell the marshaller the flow direction and the amount of data to be transfered.

6.2.        Data Marshalling and Parameter Direction Attrubutes

Data marshaller's job is to allocate memory for the destination, and copy data from the corresponding source memory block to the allocated memory. The source and destination is decided by the parameter direction attributes [in] and [out]. The amount of memory to be allocated is decided by the type definition of the parameter and – if the parameter has a variable size like a variable-size array or structure – the [size_is] attribute.

Notice that when the parameter is a pointer, the marshaller will not only allocate memory for the pointer itself, but also for the memory block pointed by the pointer. Otherwise the destination side can not make any use of the pointer.

When the direction attribute is [in], before the server method is called, marshaller allocates memory for the server and copies data from the client to server. When the attribute is [out], marshaller allocates memory for the client and copies data from server to it after the server method returns. When the attribute is [in, out], the marshaller does the [in] process before the method call, then the [out] process after the method returns.

Because the marshaller copies data from the source to the allocated memory of the destination, the source must have allocated the corresponding memory before the marshalling process starts. If not, for example, the source only uses a pointer to NULL to call the destination, then run-time error will happen when marshaller accesses the memory pointeded by the source pointer and tries to copy data from it.

Becaue the marshaller usually needs to copy some data from server's memory back to the client after the server method returns, the server should never delete any allocated memory – no matter it is allocated by the server itself or the marshaller. The marshaller will deallocate all server memory after it finishes the marshalling process. In comparison, the client should always deallocate memory – no matter it is allocated by the client itself or the marshaller.

Just as in-process method calls, if you want to get result from the called function, you must pass the parameter by pointer. If it is passed by value, the server may modify his copy of the parameter but the client-side parameter will remain unchanged. If the object to be passed itself is a pointer, then it should be passed by a double pointer.

6.3.        CoTaskMemAlloc & CoTaskMemFree

To safely allocate the memory that may be accessed by the marshaller, or deallocate memory allocated by the marshaller, use method CoTaskMemAlloc and CoTaskMemFree, which are known by the marshaller:

 

LPVOID CoTaskMemAlloc(ULONG ulSize);

void CoTaskMemFree(void *pv);

The initial contents of the memory block returned by CoTaskMemAlloc are undefined. It may be larger than required because of the space required for alignment and for maintenance information.

If the required memory size is zero, CoTaskMemAlloc allocates a zero-length item and returns a valid pointer to that item. If there is insufficient memory available, CoTaskMemAlloc returns NULL. Therefore, applications should always check the return value from this method, even when requesting small amounts of memory.

To initialize the allocated memory, use C run time library method memset. Notice that it is always safer to initialize the allocated memory before passing it to anywhere. Errors may be caused by passing uninitialized memory.

Example:

 

short sSize = 100;

short * pShort;

pShort = reinterpret_cast<short *>(CoTaskMemAlloc(sSize * sizeof(short)));

if(pShort == NULL)

{...}

::memset(pShort, 0, sSize);

...

CoTaskMemFree(pShort);

6.4.        Example of Passing Arrays

¨    IDL

 

import "oaidl.idl";

import "ocidl.idl";

 

    [

object,

uuid(5E9396EC-ECA4-45F7-BEB1-EC56C381248A),

   

helpstring("IAny Interface"),

pointer_default(unique)

    ]

    interface IAny : IUnknown

    {

// Passing info from client to server

[helpstring("method PShortsClientAlloc")]

HRESULT PassIn(

[in] short count,

[in, size_is(count)] short * pShort);

 

// Passing info from server to client

[helpstring("method GShortsServerAlloc")]

HRESULT PassOut(

[out] short * pCount,

[out, size_is(1, *pCount)] short ** ppShort);

 

// passing info bidirectionally

[helpstring("method PassArray")]

HRESULT PassBirect(

[in, out] short * psSize,

[in, out, size_is(1, *psSize)] short ** pps);   

    };

 

[

uuid(BA65DD03-CEBC-4E7A-A968-033C1C542FB7),

version(1.0),

helpstring("Server 1.0 Type Library")

]

library SERVERLib

{

importlib("stdole32.tlb");

importlib("stdole2.tlb");

 

    [

uuid(963BC191-1265-4E90-A330-13787BCA9437),

helpstring("Any Class")

    ]

coclass Any

    {

[default] interface IAny;

    };

};

¨    Server implementation

 

STDMETHODIMP CAny::PassIn(short count, short * pShort)

{

char buf[80];

for(short i = 0; i < count; i++)

    {

sprintf(buf, "Passed from client: %d", pShort[i]);

if((i % 1000) == 0)

::MessageBox(NULL, buf, "Server", MB_OK);

    }

return S_OK;

}

 

STDMETHODIMP CAny::PassOut(short * pCount, short ** ppShort)

{

short sSize = 10;

(*pCount) = 10;

(*ppShort) = (short *)CoTaskMemAlloc(sSize * sizeof(short));

if((*ppShort) == NULL)

return E_OUTOFMEMORY;

   

for(short i = 0; i < sSize; i++)

(*ppShort)[i] = i;

 

return S_OK;

}

 

STDMETHODIMP CAny::PassBidirect(short * pSize, short **pps)

{

char buf[80];

for(short i = 0; i < *pSize; i++)

    {

        sprintf(buf, "Passed from client:\n*pps[%d] = %d", i, (*pps)[i]);

::MessageBox(NULL, buf, "Server", MB_OK);

    }

 

    CoTaskMemFree(*pps);

short sSize = 10;

    (*pps) = (short *)CoTaskMemAlloc(sSize * sizeof(short));

for(i = 0; i < sSize; i++)

    {

(*pps)[i] = i * i;

    }

    (*pSize) = sSize;

return S_OK;

}

¨    Client implementation

 

void CClientDlg::OnPassIn()

{

const short sSize = 7001;

short * pShort;

pShort = (short *)CoTaskMemAlloc(sSize * sizeof(short));

if(pShort == NULL)

    {

::MessageBox(NULL, "CoTaskMemAlloc failed!", "Client", MB_OK);

return;

    }

 

for(short i = 0; i < sSize; i++)

pShort[i] = i;

 

HRESULT hr = m_pAny->PassIn(sSize, pShort);

if(FAILED(hr))

        ::MessageBox(NULL, "PassShorts failed!", "Client", MB_OK);

 

    CoTaskMemFree(pShort);

}

 

void CClientDlg::OnPassOut()

{

short sSize;

    short * pShort = NULL;

 

HRESULT hr = m_pAny->PassOut(&sSize, &pShort);

if(FAILED(hr))

::MessageBox(NULL, "PShortsServerAlloc failed!", "Client", MB_OK);

 

char buf[80];

for(short i = 0; i < sSize; i++)

    {

sprintf(buf, "Got from server: %d", pShort[i]);

if((i % 1000) == 0)

::MessageBox(NULL, buf, "Client", MB_OK);

    }

 

    CoTaskMemFree(pShort);

}

 

void CClientDlg::OnPassBidirect()

{

sSize = 5;

short * ps = (short *)CoTaskMemAlloc(sSize * sizeof(short));

for(i = 0; i < sSize; i++)

ps[i] = i;

 

    hr = pAny->PassBidirect(&sSize, &ps);

    if(FAILED(hr))

    {

::MessageBox(NULL, "PassArray failed!", "Client", MB_OK);

return FALSE;

    }

 

char buf[80];

for(i = 0; i < sSize; i++)

    {

sprintf(buf, "Passed from client:\nps[%d] = %d", i, ps[i]);

::MessageBox(NULL, buf, "Server", MB_OK);

    }

    CoTaskMemFree(ps);

}

6.5.        Example of Passing Variable-Length Custom Types

¨    IDL

 

import "oaidl.idl";

import "ocidl.idl";

 

    typedef struct {

        short sSize;

        [size_is(sSize)] short sArray[];

    } Group;

 

 

    [

        object,

uuid(9BF19302-65E6-4218-8C6F-55C3816123F5),

   

        helpstring("IAny Interface"),

        pointer_default(unique)

    ]

    interface IAny : IUnknown

    {

[helpstring("method StructInOut")]

        HRESULT StructInOut([in, out] Group ** ppg);

    };

 

[

    uuid(185CB840-2F73-4095-A165-8FAFEDA1CBF8),

    version(1.0),

    helpstring("Marshal 1.0 Type Library")

]

library MARSHALLib

{

    importlib("stdole32.tlb");

    importlib("stdole2.tlb");

 

    [

        uuid(27FE57E8-26EF-4E41-AEB9-79DE4079F0E1),

        helpstring("Any Class")

    ]

    coclass Any

    {

        [default] interface IAny;

    };

};

¨    Server implementation

 

STDMETHODIMP CAny::StructInOut(Group **ppg)

{

    char buf[100];

    for(short i = 0; i < (*ppg)->sSize; i++)

    {

sprintf(buf, "(*ppg)->sArray[%d] = %d", i, (*ppg)->sArray[i]);

        M(buf); // Macro to simplify function ::MessageBox

    }

 

    CoTaskMemFree(*ppg);

 

    short sSize = 20;

    (*ppg) = (Group *)CoTaskMemAlloc(sSize * sizeof(short) + sizeof(Group));

    if((*ppg) == NULL) // must check the result of memory allocation

    {

        ::MessageBox(NULL, " CoTaskMemAlloc failed!", "Server", MB_OK);

        return E_OUTOFMEMORY;

    }

 

    (*ppg)->sSize = sSize;

    for (i = 0; i < (*ppg)->sSize; i++)

        (*ppg)->sArray[i] = i * i;  

 

    return S_OK;

}

¨    Client implementation

 

void CClientDlg::OnButton3()

{

    short lSize = 5;

Group * pg;

    pg = (Group *)CoTaskMemAlloc(lSize * sizeof(short) + sizeof(Group));

    if(pg == NULL)

    {

        M("CoTaskMemAlloc failed!");

        return;

    }

    pg->sSize = lSize;

    for(short i = 0; i < lSize; i++)

        pg->sArray[i] = i;

 

    HRESULT hr = m_p->StructInOut(&pg);

    if(FAILED(hr))

        M("StructInOut failed");

 

    char buf[100];

    for(i = 0; i < pg->sSize; i++)

    {

        sprintf(buf, "pg->sArray[%d] = %d", i, pg->sArray[i]);

        M(buf);

    }

 

    CoTaskMemFree(pg);

}

6.6.        Proxy/StubClasses

A proxy resides in the client's process and represents the server,  and a stub resides in the server's process to receive data sent from the proxy and send server's response back. They are all standard COM objects residing in a DLL server. On such DLL server may contain many proxy/stub classes.

Each interface in a server is represented by a separate proxy and stub coclass. Each proxy coclass implements two interfaces: the one which it represents and a standard interface IRpcProxyBuffer, which has method Connect and Disconnect.

Channel object implementing interface IRpcChannelBuffer is responsible to conduct the data transfer between the proxy and the stub. Proxy manager is responsible to connect proxy objects to the channel object, and assemble the proxy objects representing different interfaces of one coclass together using COM aggregation, so that the cluster of proxy coclasses appears to the client as a cluster of interfaces of one coclass.

A stub implements IRpcStubBuffer and is managed by the stub manager. Stub manager doesn't aggregate the stubs.

Class factories implement interface IPSFactoryBuffer. One class factory is responsible to create both the proxy and stub for a given server coclass with its method CreateProxy and CreateStub.

6.7.        Marshalling Options

¨    Custom Marshalling

If you choose to write all marshalling code yourself, you should let your coclass implement interface IMarshal. SCM will query for IMarshal first. This marshalling option is called custom marshalling.

¨    Standard Marshalling

This is an easier option. When MIDL compiles the IDL file it will generate all files that is needed to build the proxy/stub DLL, such as Shape_i.c, Shape.h, Shape_p.c and dlldata.c. ATL AppWizard can generate a make file which can automatically build the proxy/stub DLL from the above four files. To build the proxy/stub DLL server and register it:

 

nmake ProjectNameps.mk

regsvr32 ProjectNameps.dll

You can put the last two commands in mene “Project” | “Settings” | “Custom build”. This way every time you build the project the rest is done automatically.

When client invokes an interface, SCM will find the interface entry under HKCR\Interface, then the ProxyStubClsid32 entry under it, which points to the corresponding custom proxy/stub DLL. Then SCM will call the class factory and instantiate a proxy/stub object to prepresent the interface.

¨    Universal Marshalling

The third option is to use the universal marshaller oleaut32.dll. The ProxyStubClsid32 entries of all interfaces should point to this universal marshaller, and the TypeLib entries point to the registered type library. The universal marshaller will find the registered type library of the invoked interface, read the type information in it, and create the proxy/stub DLL on the fly.

To use universal marshaller, you have to:

1.       Put attribute [oleautomation] in front of all interfaces and make them variant compatible;

2.       Register the type library;

3.       Register all interfaces and set the value of ProxyStubClsid32 entry under each interface to the guid of oleaut32.dll and the value of TypeLib entry to your type library guid.

You can also do all the above things through a function call at run time:

 

ITypeLib * ptlb = NULL;

LoadTypeLibEx(L”Shapes.tlb”, REGKIND_REGISTER, &ptlb);

ptlb->Release();

If there is [oleautomation] attribute in front of all interfaces in the IDL file, this function will point the ProxyStubClsid32 entry to the universal marshaler.

6.8.        Hosting an DLL in a Surrogate Process

To host a DLL in an EXE housing named dllhost.exe, you need to have an AppID for the DLL server and have a DllSurrogate entry under it. When its value is "", it means that we use the default surrogate dllhost.exe.

To automatically assign an AppID and add a DllSurrogate entry under it, open the DLL object under "All Object" entry of OLE/COM Object Viewer. Tick the "Use Surrogate Process" option. The COM objects in the DLL server must support aggregation.

7.      DCOM

7.1.        Configuring a Server to be Invoked Remotely

There are three major issues to configure for a server to be invoked remotely. The first two can be configured machine-wide, so that you decide the settings for the machine, and all servers which has not been specifically configured take the machine-wide settings. All these things can be done through dcomcnfg.exe.

¨    Authentication Level

Determines how often and how strict does the COM run time check for the server the identification of the client.

¨    Security

Includes Access Permission, Launch Permission and Configuration Permission. Each server has an AppID to store server-wide attributes. In System Registry, each AppID has a Access Control List (ACL), which lists which client has which permssion.

¨    Server Identify

A server is launched with an user account identity. It has three options:

1.    The Interactive User: all servers in a machine are launched with the identity of the person who is logged on to this machine. This is only used in debugging session when you want the server to display some GUI such as a message box. If you do not choose this option the server can not display any GUI on the machine and will appear pending.

2.    The Launching User: the server always has the identify of the person who launched it. As a side effect, the SCM will launch a new instance of the server for each client.

3.    This User: proper for production application. You select one or a group of users as the identity of the server.

7.2.        Configuring a Client to Call Remote Server

The server should be registered on the client machine. You can configure the client to call remote server with dcomcnfg.exe. Choose a server and double-click it to bring up the properties of the server. In the "Location" tab, choose correct option. You can tick either or both of the options: "Run application on this computer" or "Ran application on the following computer", and enter the name of the remote computer. If you choose both, the SCM will choose the nearest one. The name of the computer will be assigned to a new entry RemoteServerName under HKCR\AppID. When SCM sees this entry, it knows that it's going remote.

You can also configure a client through function CoCreateInstanceEx. It will suppress the dcomcnfg.exe settings:

 

COSERVERINFO serverInfo = {0};

serverInfo.pwszName = L"FLIU";

 

MULTI_QI interfaces[2] = {0};

interfaces[0].pIID = &IID_IFirst;

interfaces[1}.pIID = &IID_ISecond;

 

CoCreateInstanceEx(

CLSID_MyClass,

NULL,

CLSCTX_REMOTE_SERVER,

&serverInfo,

2,  // Size of the MULTI_QI array

interfaces);

 

if(FAILED(interfaces[0].hr))

{

::MessageBox(NULL, "Query for IID_IFirst failed!", "Client", MB_OK);

return;

}

else if(FAILED(interfaces[1].hr))

{

::MessageBox(NULL, "Query for IID_IFirst failed!", "Client", MB_OK);

return;

}

 

IFirst * pf = (IFirst *)interfaces[0].pItf;

ISecond * ps = (ISecond *)interfaces[1].pItf;

...

To be able to call the DOM functions in COM library such as CoInitialize, CoInitializeEx or CoCreateInstanceEx, you should put the following line before including <windows.h> or in the project-wide #define settings:

 

#define _WIN32_DCOM

Function CoCreateInstanceEx uses two structures:

 

WINOLEAPI CoCreateInstanceEx(

REFCLSID Clsid,

IUnknown * punkOuter,        // only relevant locally. Normally NULL

DWORD dwClsCtx,              // CLSCTX

COSERVERINFO * pServerInfo,  // contains server computer name

DWORD dwCount,               // size of MULTI_QI array

MULTI_QI * pResults );       // array of MULTI_QI

Structure COSERVERINFO holds the server computer name:

 

typedef struct  _COSERVERINFO

{

DWORD dwReserved1;

    LPWSTR pwszName;

    COAUTHINFO __RPC_FAR *pAuthInfo;

DWORD dwReserved2;

} COSERVERINFO;

The array of structure MULTI_QI hold the IIDs of the wanted interfaces, the returned IUnknown pointers and HRESULTs:

 

typedef struct  tagMULTI_QI

{

    const IID __RPC_FAR *pIID;

    IUnknown __RPC_FAR *pItf;

    HRESULT hr;

} MULTI_QI;

8.      ATL Basics

8.1.        ATL Macro STDMETHOD & STDMETHODIMP

ATL uses macro STDMETHOD to define a method. It is defined as:

 

#define STDMETHOD(method)       virtual HRESULT STDMETHODCALLTYPE method

#define STDMETHODCALLTYPE       __stdcall

Therefore,

 

STDMETHOD(Calculate)(int x, float y)

is in fact

 

virtual RESULT __stdcall Calculate(int x, float y)

STDMETHODIMP is used to mark the implementation of a method. It is defined as:

 

#define STDMETHODIMP            HRESULT STDMETHODCALLTYPE

8.2.        Creating DLL Server with ATL

¨    1.  Create a project

In AppWizard, choose ATL COM AppWizard and DLL option. It will create those entry points for the project. No coclass is generated yet.

¨    2.  Add a coclass

Choose “Insert” | “New ATL Object”. Give the short name “Account”. MFC will create a coclass with the initial default interface. The class name will be CAccount, coclass name Account, interface name IAccount.

¨    3.  Add methods to the interface

Right-click the interface symbol e.g. IGreet under the class name CAccount. The dialog prompt out will be different from normal classes. Return type is by default HRESULT, and any parameter should start with [in] or [out] indicating whether this parameter is to pass info into or out of the COM, such as “[in] int amount” or “[out] int * balance”.

¨    4.  Add data members to the coclass

¨    5.  Implement the methods

The skeletons of the methods have already been set up by the wizard.

¨    6.  Add more interfaces

Add the definition of a new interface into the ProjectName.IDL file, such as

 

[

object,

uuid(0D6380A1-9FC8-4897-9621-47EFE50C8887),

helpstring("IDisplay Interface"),

    pointer_default(unique)

]

interface IDisplay : IUnknown

{

[helpstring("method Show")] HRESULT Show();

};

You need to generate the guid with “Microsoft Visual Studio\Common\Tools\Guidgen.exe”.

Also add the interface name to the coclass definition in this IDL file.

¨    7.  Modify coclass definition for additional interfaces

Let the coclass inherit from the new interfaces too. Add the COM_INTERFACE_ENTRY for the new interfaces in the COM_MAP.

¨    8.  Repeat step 3 ~ 6

8.3.        Manually Registering ATL DLL Servers

 A DLL COM server is supposed to export DllRegisterServer and DllUnregisterServer to register and unregister the server. You can invoke these functions using tool regsvr32.exe.

To register a DLL server:

 

regsvr32 path\bank.dll

To unregister a DLL server:

 

regsvr32 /u path\bank.dll

ATL makes it even more convenient: it inserts the following lines into the menu “Project” | “Settings” | “Custom Build” option:

 

regsvr32 /s /c "$(TargetPath)"

echo regsvr32 exec. time > "$(OutDir)\regsvr32.trg"

So that after each build the server will be automatically registered.

8.4.        Supporting MFC in ATL Project

When creating a new DLL ATL project with ATL COM AppWizard, you can tick the "Support MFC" option. But this binds your code with MFC and you have to ship the MFC runtime dll is shipped with your product. So you should always try not to select this option.

If you create an EXE ATL project, the "Support MFC" option is disabled. You have to add MFC support yourself.

8.5.        Creating EXE Server with ATL

1.        Create an EXE server project with ATL COM AppWizard;

2.        Add an ATL object;

3.        Add methods to the primary interface of the ATL object;

4.        Add more interfaces and methods in the same way as with DLL server;

5.        Add the commands to make and register the proxy/stub DLL into custom build;

6.        Build the project.

8.6.        Manually Registering ATL EXE Servers

Just like a DLL server, an EXE server also supports self registration. Simply run the program with command line argument “/RegServer” to register or “/UnregServer” to unregister:

 

ProjectName /RegServer

8.7.        Removing an ATL Coclass

You can use ATL Object Wizard to insert new colcasses into the server, but to remove one added with ATL, you have to do the following things manually:

1.    In coclass.cpp, remove the #include of the coclass.h file;

2.    In coclass.cpp, remove the OBJECT_ENTRY of the coclass;

3.    Remove the code in IDL file;

4.    Open the project's *.rc file with Wordpad or Notepad, locate the section beginning with "//REGISTRY", and remove the line containing the coclass.rgs.

5.    Remove the *.cpp, *.h and *.rgs files of the coclass.

8.8.         Implementing an Interface in ATL

To implement an interface defined in the project's IDL file, you have to

1.    In the *.h file, let your coclass inherit from the interface,

2.    In the *.h file, add an interface entry in the COM map,

3.    In the *.h file, add the protoptypes of the interface methods:

4.    In the *.cpp file, implement the methods of the interface.

 

//**************** Any.h ********************

class ATL_NO_VTABLE CAny :

public CComObjectRootEx<CComSingleThreadModel>,

public CComCoClass<CAny, &CLSID_Any>,

    public IMyInterf  // step 1

{

public:

    CAny()  {}

 

DECLARE_REGISTRY_RESOURCEID(IDR_ANY)

DECLARE_NOT_AGGREGATABLE(CAny)

DECLARE_PROTECT_FINAL_CONSTRUCT()

 

BEGIN_COM_MAP(CAny)

    COM_INTERFACE_ENTRY(IMyInterf)  // step 2

END_COM_MAP()

 

public:

    STDMETHOD(Hi)();  // step 3

};

 

//************** Any.cpp ********************

STDMETHODIMP CAny::Hi()  // step 4

{

  return S_OK;

}

You can also do all the above four steps automatically with ATL's "Implement Interface Wizard". Right-click the coclass in the class view, choose "Implement Interface" option, tick the unimplemented interface listed. If you do not see it listed, you may have forgotten to compile the IDL file.

To be able to implement an interface with the wizard through type library, that interface must have been added into one coclass. For some reason, a stand-alone interface won't be displayed by the wizard.

To implement an interface defined in another server, you only need to do one extra thing: #import the type library:

 

#import "D:\My Documents\Visual C++\AnyServer\AnyServer.tlb" \

raw_interfaces_only, raw_native_types, no_namespace, named_guids

When you are using ATL's "Implement Interface Wizard", just click the "Add TypeLib..." button in the "Implement Interface" dialog. It will bring up another window containing all registered type libraries. If the type library is not yet registered, you can click the "Browse" button to browse to it. The wizard will add the #import line for you.

8.9.        Debugging ATL Projects

For debug builds, macro ATLTRACE prints argument into the debug window:

 

LONG v = 1234;

ATLTRACE("The value is %d", v);

For release builds it expends to nothing.

_ATL_DEBUG_QI preprocessor directive causes all QueryInterface calls to be writtin into the debug window. Just put in stdafx.h the following line:

 

#define _ATL_DEBUG_QI

To be able to see the interface call in form of "IAny", "IMyInterface" instead of 128-bit guids, the interfaces should be registered.

Preprocessor directive _ATL_DEBUG_INTERFACES is based on _ATL_DEBUG_QI and can display the reference count and detect mishandling of AddRef and Release.

9.      COM Threading Issues

9.1.        Multiple Thread Synchronization With Critical Section

In non-COm C++ environment, different threads in the same process can freely talk to each other. Objects that are exposed to multiple threads have to make all their data thread-safe if necessary.

Following is a multithreaded example making use of CRITICAL_SECTION to guarantee that a certain piece of code can only be accessed by one thread. The main thread creates a"ThreadParam" structure  to hold parameters to pass to the new threads it fires. The parameters include a class object and a function pointer.

 

#include "stdafx.h"

#include "iostream.h"

#include <stdio.h>

 

CRITICAL_SECTION g_cs;

 

typedef void(* Function)(int);

 

typedef struct {

    int a;

} Any;

 

typedef struct {

Function f;

    Any * pAny;

} ThreadParam;

 

void ShowNumber(int a)

{

char buf[80];

sprintf(buf, "Number = %d", a);

m(buf);

}

 

// Global thread function with arbitrary name but standard signature

DWORD WINAPI Threads(void * p)

{

    EnterCriticalSection(&g_cs);

 

ThreadParam * pThreadParam = (ThreadParam *)p;

char buf[80];

sprintf(buf, "Beginning of thread %d", (pThreadParam->pAny)->a);

m(buf);

   

    Any * pAny = pThreadParam->pAny;

Function f = pThreadParam->f;

f(pAny->a);

 

sprintf(buf, "End of thread %d", (pThreadParam->pAny)->a);

m(buf);

delete pThreadParam->pAny;

delete pThreadParam;

 

    LeaveCriticalSection(&g_cs);

return 0;

}

   

 

int APIENTRY WinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

{

    InitializeCriticalSection(&g_cs);

ThreadParam * pThreadParam;

 

for (int i = 1; i < 10; i++)

    {

pThreadParam = new ThreadParam;

        pThreadParam->pAny = new Any;

pThreadParam->f = ShowNumber;

pThreadParam->pAny->a = i;

 

DWORD dw;

HANDLE h = CreateThread

(NULL, 0, Threads, (void *)pThreadParam, 0, &dw);

CloseHandle(h);

    }

 

    Sleep(20000);

    DeleteCriticalSection(&g_cs);

return 0;

}

9.2.        COM Apartments

When a thread calls CoInitializeEx (CoInitialize maps to CoInitializeEx), it creates a COM apartment. If the second parameter of the function call is COINIT_APARTMENTTHREADED, a new Single-threaded Apartment (STA) is created. If it is COINIT_MULTITHREADED, the thread either creates a Multithreaded Apartment (MTA) if there isn’t one created, or enters an existing one. So there can be multiple STAs in a process, but there is only one MTA per process.

CoInitializeEx Initializes the COM library for use by the calling thread, sets the thread's concurrency model, and creates a new apartment for the thread if one is required [MSDN]. A thread must call CoInitializeEx before calling any other COM library function except CoGetMalloc and other memory allocation functions.

When this thread calls CoCreateInstance, depending on whether the component’s apartment type is the same, it either loads the component in the same apartment, or in a different one.

Accesses to a coclass in an EXE server through proxy/stub marshallers are automatically serialized, so there is no multithread synchronization issue when multiple clients are invoking the same EXE server. But different threads in a process can invoke concurrently the same DLL server.

A component should never create any threads on its own. This is primarily of concern to J++ and C++ developers, who have the ability to create threads directly. Developers creating objects in Visual Basic do not need to worry about this, as there is no way for them to explicitly create a thread [MSDN].

¨    STA and MTA

If one thread creates an apartment and then dispatches another thread which also needs to call COM library functions, the new thread should also call CoInitializeEx itself to create or enter another apartment. 

If one STA thread wants to pass an interface pointer of a STA component to another STA thread, you must explicitly marshal the pointer, so that the calls to the component from the two threads are synchronized by the marshaller. Otherwise they won’t be synchronized.

However, if the thread(s) and the component are in different types of apartments, you don’t need to explicitly marshal the pointer, because proxy and stub will be loaded for inter-apartment calls and they will synchroniz all calls.

If you are passing pointer to a MTA component between MTA threads, don’t marshal it, because a MTA component is meant to be accessed concurrently by multiple threads.

If we follow the above rule, we can make sure that one instance in a STA can only be accessed by one thread at the same time. However, two STA threads can access two instances of the same coclass concurrently, therefore they can access the same global or class data concurrently. So STA components must protect their global and class data. Local objects must be allocated on the stack.

If DllGetClassObject creates one class factory instance for each client request, the class factory does not need to be thread-safe. If a component uses one class factory instance for all client requests, then it needs to be thread-safe.

Becaue of the synchronization, the performance of a STA component is lower than a MTA component.

Because multiple threads can execute in an MTA concurrently, so you must protect all data in your component if necessary using critical sections or similar measures.

¨    Primary STA

Early legacy COM components are designed to be used in single-threaded environment. Their data is not protected at all. They also don't put ThreadingModel entry into the System Registry.

When COM is asked to create an instance of coclass which doesn't have a ThreadingModel entry, it will always put the instance in the first STA apartment. Therefore all legacy components are placed in the same STA apartment.  It means that if one thread is calling any method in any componet in that STA, all other threads which wants to invoke any component have to wait. Therefore these components are 100% thread safe, including their global and class data. But there performance is the worst.

¨    Confusion

Now that calls between STA and MTA are synchronized by proxy/stub, and calls from two STA threads to a STA component has to be synchronized manually, what’s the use of STA’s message pump?

9.3.        How do Components Declare Their Threading Model

When a coclass registers itself with *.rgs file in the system registery, it can register itself as one of four threading model by adding (or not adding) a ThreadingModel entry under HKCR\CLSID\<clsid>\InprocServer32:

1.         Primary STA        no ThreadingModel entry,

2.         STA                    ThreadingModel = "Apartment",

3.         MTA                    ThreadingModel = "Free",

4.         Both                   ThreadingModel = "Both",

When you create a coclass (not a DLL or EXE housing) using ATL COM Wizard, you will be offered a chance to select from the above four options. Then the wizard will automatically generate a rgs file containing the correct registry entry.

Note that although a coclass can be dealt with by COM run time in the above four different ways, the coclass itself can be implemented in only two ways: STA or MTA. When using ATL it is determined by inheriting from template class CComObjectRootEx<CComSingleThreadModel> or CComObjectRootEx<CComMultiThreadModel>.

Especially, if you choose "Both" option, your component must aggregate with the free-threaded marshaller provided by COM. To do it, tick the "Free-threaded marshaller" option when you create an ATL object with the ATL Object Wizard.

9.4.        How Does COM Choose Apartments for Components at Run Time

A DLL coclass declares what kind of thread it can safely live in by putting a ThreadingModel entry in the System Registry.

Before a client thread invokes a component, it must first create a new apartment (can be STA or MTA) or enter an existing apartment (MTA). Then when it asks COM run time to load a COM component, COM will compare the apartment type of the client and the component, to decide whether to load the component in the same apartment as the client and let the client directly access it, or create a new apartment for the component alone and let the client talk to the component through proxy/stub marshallers, which is less efficient than the first option.

To achieve better performance, COM run time will always try to load the component in the same apartment as the client. Only when the component declares that "I can only stay in xxx apartment" which is different from the apartment of the client, will COM create a new apartment for the component.

If a component declares itself to be “Primary STA”, then COM always loads it in primary STA. Unless the client thread is also in the primary STA, proxy access is needed.

If a component is “STA”, and the client is in primary STA or a normal STA, COM will create the component instance in the same apartment as the client so that no proxy access is needed. If the client is in MTA, proxy access is needed.

If a component is “Free”, and client is MTA, then the component is loaded into the process-unique MTA, and the client can directly access the component without any overhead. However, if client is single-threaded, then a proxy access is needed. This is not good.

If a component is "Both", then a directly access is always guaranteed, because COM always creates the component instance in the same apartment as the client, no matter the client is in primary STA, STA or MTA. This is the best component threading model, the cost is you have to provide full synchronization protection for your code.

9.5.        Proxy/stub

Any communication across the apartment boundary needs the help of a proxy/stub DLL server, which is generated and registered with nmake and regsvr32 exactly as you do with EXE servers.

9.6.        ATL's Threading Support with CComObjectRootEx<>

ATL's support for thread protection i.e. synchronization at the end comes to four methods: Increment and Decrement which are used to increment and decrement the object reference count m_refCount, and Lock and Unlock which wraps proected code and extends to critical section function calls.

An ATL coclass always inherits from CComObjectRootEx<>, which takes one threading model class as template parameter, which can either be CComSingleThreadModel (for STA coclass) or CComMultiThreadModel (for MTA coclass).  These two classes has exactly the same interfaces. They contain method Increment and Decrement, and the typedef of three other classes which contain method Lock and Unlock.

CComObjectRootEx<> itself provides four thread protection methods: InternalAddRef, InternalRelease, Lock, Unlock, which all turn around and call methods of the passed threading model class to deal with threading issues. Method InternalAddRef and InternalRelease are called by the CComObject<> template's IUnknown method AddRef and Release, and Lock and Unlock are used to enter and leave critical sections.

InternalQueryInterface is provided elsewhere by CComObjectRootBase.

 

template <class ThreadModel>

class CComObjectRootEx : public CComObjectRootBase

{

public:

    typedef ThreadModel _ThreadModel;

typedef _ThreadModel::AutoCriticalSection _CritSec;

typedef CComObjectLockT<_ThreadModel> ObjectLock;

 

ULONG InternalAddRef()

    {

ATLASSERT(m_dwRef != -1L);

return _ThreadModel::Increment(&m_dwRef);

    }

ULONG InternalRelease()

    {

ATLASSERT(m_dwRef > 0);

return _ThreadModel::Decrement(&m_dwRef);

    }

 

void Lock() {m_critsec.Lock();}

void Unlock() {m_critsec.Unlock();}

private:

_CritSec m_critsec;

};

Because the two classes has the same interfaces, if you want to change the threading model of an existing component, all you need to do is simply swapping between CComSingleThreadModel and CComMultiThreadModel.

In class CComSingleThreadModel which is used for STA components, method Increment and  Decrement simply use "++" or "- -" operation on the object reference count, and it typedefs CComFakeCriticalSection as the classes providing Lock and Unlock, which does nothing:

 

class CComSingleThreadModel

{

public:

static ULONG WINAPI Increment(LPLONG p) {return ++(*p);}

static ULONG WINAPI Decrement(LPLONG p) {return --(*p);}

 

typedef CComFakeCriticalSection AutoCriticalSection;

    typedef CComFakeCriticalSection CriticalSection;

typedef CComSingleThreadModel ThreadModelNoCS;

};

 

class CComFakeCriticalSection

{

public:

void Lock() {}

void Unlock() {}

void Init() {}

void Term() {}

};

Because CComSingleThreadModel  actually provides no useful thread-protecting code at all, you have to use cirtical section functions to protect your global and static data.

In comparison, in class CComMultiThreadModel which is used for MTA components, method Increment and Decrement uses API function InterlockedIncrement and InterlockedDecrement to safely operate on data member m_refCount, and it typedefs CComAutoCriticalSection to provide method Lock and Unlock, which extends to critical section function calls:

 

class CComMultiThreadModel

{

public:

  static ULONG WINAPI Increment(LPLONG p) {return InterlockedIncrement(p);}

  static ULONG WINAPI Decrement(LPLONG p) {return InterlockedDecrement(p);}

 

  typedef CComAutoCriticalSection AutoCriticalSection;

  typedef CComCriticalSection CriticalSection;

  typedef CComMultiThreadModelNoCS ThreadModelNoCS;

};

 

class CComAutoCriticalSection

{

public:

  void Lock() {EnterCriticalSection(&m_sec);}

  void Unlock() {LeaveCriticalSection(&m_sec);}

  CComAutoCriticalSection() {InitializeCriticalSection(&m_sec);}

  ~CComAutoCriticalSection() {DeleteCriticalSection(&m_sec);}

  CRITICAL_SECTION m_sec;

};

To protect other data in the coclass, simply call method Lock and Unlock.

9.7.        How do Clients Decide Their Threading Model

Because all COM components must be placed in apartments, if a thread wants to access a component, it must create an apartment or enter an existing one. This is done by calling function CoInitializeEx passing NULL and COINIT_MULTITHREADED for MTA or COINIT_APARTMENTTHREADED for STA. Before you use CoInitializeEx, make sure you have defined _WIN32_DCOM.

9.8.        Marshalling Interface Pointer Between Appartments

The rule is: when you move an interface pointer across the apartment boundary, it must be marshalled. When you move an interface pointer through COM API function calls such as CoCreateInstance or QueryInterface, or pass it as a parameter in a COM method call, COM run time will take care of the marshalling. However, when COM is not involved in the marshalling, you have to marshal the interface pointer yourself.

In the following example, COM server Threads defines an call-back interface ICallBack. Another COM server implements ICallBack interface. The client creates an instance of both of the servers, call server Threads's Advise method to pass it the pointer to the implemented ICallBack. Then the client calls coclass Source's DoWork method, which spawns a new thread passing the ICallBack pointer. The thread will call the call back method of ICallBack to do its job.

In this example, coclass Source is in STA, while the thread it creats enters the MTA. Some book says in such case because the calling through interface pointer is not between client and server and thus not taken care of by COM run time, you have to marshal the interface yourself. So I did this in this example, but it works fine even if you don't marshal it at all!

 

STDMETHODIMP CSource::Advise(ICallBack *pCallBack)

{

m_pCallBack = pCallBack;

return S_OK;

}

 

STDMETHODIMP CSource::DoWork()

{

    PARAM * pParam =

(PARAM *)CoTaskMemAlloc(sizeof(PARAM) + 50000 * sizeof(LONG));

pParam->sSize = 50000;

for(LONG i = 0; i < 50000; i++)

pParam->sArray[i] = i;

 

IStream * ps = NULL;

    HRESULT hr = CoMarshalInterThreadInterfaceInStream

(IID_ICallBack, m_pCallBack, &ps);

pParam->ps = ps;

 

DWORD dw;

HANDLE h = CreateThread(0, 0, AThread, (void *)pParam, 0, &dw);

    CloseHandle(h);

 

Sleep(3000);

return S_OK;

}

 

// Global thread function with arbitrary name but standard signature

DWORD WINAPI AThread(void * p)

{

    CoInitializeEx(NULL, COINIT_MULTITHREADED);

PARAM * pParam = (PARAM *)p;

ICallBack * pCallBack = NULL;

HRESULT hr = CoGetInterfaceAndReleaseStream

(pParam->ps, IID_ICallBack, (void **)&pCallBack);

 

char buf[80];

for(LONG i = 0; i < pParam->sSize; i++)

    {

if(i % 10000 == 0)

{

sprintf(buf, "pParam->sArray[%d] = %d", i, pParam->sArray[i]);

            m(buf);

}

    }

 

LONG * ps = (LONG *)CoTaskMemAlloc(100000 * sizeof(LONG));

for(i = 0; i < 100000; i++)

ps[i] = i;

pCallBack->CallBackMethod(100000, ps);

 

    CoUninitialize();

return 0;

}

9.9.        ATL Server's Default Threading Support

Every project generated with ATL COM AppWizard defines a default threading model in the precompiled header file stdafx.h. This model accounts for threading issues that you do not need to concern, such as how ATL locks its global data:

 

#define _ATL_APARTMENT_THREADED

There are three options: _ATL_SINGLE_THREADED, _ATL_APARTMENT_THREADED and _ATL_FREE_THREADED.

In <atlbase.h>, there are following typedefs based on the above definition:

 

#if defined(_ATL_SINGLE_THREADED)

  typedef CComSingleThreadModel CComObjectThreadModel;

  typedef CComSingleThreadModel CComGlobalsThreadModel;

 

#elif defined(_ATL_APARTMENT_THREADED)

  typedef CComSingleThreadModel CComObjectThreadModel;

  typedef CComMultiThreadModel CComGlobalsThreadModel;

 

#else

  typedef CComMultiThreadModel CComObjectThreadModel;

  typedef CComMultiThreadModel CComGlobalsThreadModel;

#endif

Therefore, the definition of default threading model must proceed the include of <atlbase.h>.

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

REFIID                     iid, // 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 the  queried 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_END          0

#define _ATL_CATMAP_ENTRY_IMPLEMENTED  1

#define _ATL_CATMAP_ENTRY_REQUIRED     2

 

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, { 0x98, 0x67, 0xef, 0x7, 0x5, 0x8, 0xba, 0x30 } };

 

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

static const GUID CATID_JUST_KIDDING =

{ 0xfcfa7081, 0x93b, 0x11d6, { 0x98, 0x67, 0xef, 0x7, 0x5, 0x8, 0xba, 0x30 } };

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 ISupportErrorInfo and 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 interface  which 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 the  methods 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 {

WORD  wCode;

WORD  wReserved;

BSTR  bstrSource;

    BSTR  bstrDescription;

BSTR  bstrHelpFile;

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:

 

HRESULT  Invoke(

  DISPID dispId,

  REFIID riid,   // reservered for future use, pass IID_NULL

  LCID   lcid,   // Language ID (English, Arabic, etc.)

                 // Can be LOCALE_SYSTEM_DEFAULT

  WORD   wFlags, // 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 {

Byte                bVal;            // VT_UI1

Short               iVal;            // VT_I2

long                lVal;            // VT_I4

float               fltVal;          // VT_R4

      double              dblVal;          // VT_R8

      VARIANT_BOOL        boolVal;         // VT_BOOL

SCODE               scode;           // VT_ERROR

CY                  cyVal;           // VT_CY

DATE                date;            // VT_DATE

BSTR                bstrVal;         // 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

char                cVal;            // VT_I1

unsigned short      uiVal;           // VT_UI2

unsigned long       ulVal;           // VT_UI4

int                 intVal;          // VT_INT

unsigned int        uintVal;         // 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 struct  tagSAFEARRAY

{

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 struct  tagSAFEARRAYBOUND

{

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 of  IDispatch 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, 0x98, 0x67, 0x98, 0x80, 0xab, 0x8d, 0x81, 0x30);

 

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

DEFINE_GUID(IID_IInterf2, 0x211fe1e2, 0xfa21, 0x11d5, 0x98, 0x67, 0x98, 0x80, 0xab, 0x8d, 0x81, 0x30);

 

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()

};