COM Tutorial

COM Tutorial

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,{0×90,0x3D,0×00,0×10,0x5A,0xA4,0x5B,0xDC}};

 

const IID LIBID_BANKLib

= {0x0FFBDAA1,0xFCA7,0x11D2,{0x8F,0xF4,0×00,0×10,0x5A,0xA4,0x5B,0xDC}};

 

const CLSID CLSID_Account

= {0x0FFBDAAE,0xFCA7,0x11D2,{0x8F,0xF4,0×00,0×10,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, 0×97,

0xea, 0×2, 0x9d, 0xcd, 0xf);

 

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

DEFINE_GUID(IID_IStatus,

 

0xd518b0bf, 0x3ee1, 0×4976, 0x9b, 0x6a, 0x9f,

0×34, 0×43, 0xa2, 0xa1, 0×86);

 

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

DEFINE_GUID(CLSID_Car,

 

0x2f481e63, 0xc189, 0x4d99, 0xa7, 0×5, 0x9f,

0x3f, 0x2d, 0xfb, 0×71, 0×45);

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, 0×97, 0xea, 0×2, 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 STDMETHODIMPHRESULT 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,

LPSTRlpCmdLine,

intnCmdShow)

{

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,

LPSTRlpCmdLine,

intnCmdShow)

{

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.hInterface

definition

2.       

Shapes.tlbbinary

type library

3.       

Shapes_i.cdefinition

of all guids

4.       

Shapes_p.c and dlldate.cused 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 structtagMULTI_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 STDMETHODIMPHRESULT 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” structureto 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 STAno

ThreadingModel entry,

2.        

STAThreadingModel

= “Apartment”,

3.        

MTAThreadingModel

= “Free”,

4.        

BothThreadingModel

= “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 andDecrement

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 CComSingleThreadModelactually 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;

}

 

Leave a Reply

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

*

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