Silan Liu
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.
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
Each coclass and interface has its guid, which is a 128-bit number. For easy use by the programmer, an easy-remember manifest constant is defined for each guid. In Visual C++ these constants are defined in a header file e.g. “Bank_i.c”, and some entries look like:
const IID IID_IGreet = {0x7A5E6E81,0x3DF8,0x11D3,{0x90,0x3D,0x00,0x10,0x5A,0xA4,0x5B,0xDC}};
const IID LIBID_BANKLib = {0x0FFBDAA1,0xFCA7,0x11D2,{0x8F,0xF4,0x00,0x10,0x5A,0xA4,0x5B,0xDC}};
const CLSID CLSID_Account = {0x0FFBDAAE,0xFCA7,0x11D2,{0x8F,0xF4,0x00,0x10,0x5A,0xA4,0x5B,0xDC}};
GUID constants such as IID_IGreet and CLSID_Account are not globally unique, but it does not matter because they live only in the scope of one application program. The compiler will read the interface definition file to convert the constant to the corresponding guid.
GUIDs for classes are of type CLSID, and all variables start with “CLSID_” followed by the class name, such as “CLSID_Account”. GUIDs for interfaces are of type IID, and all start with “IID_” followed by the interface name such as “IID_IAccount”.
To convert between CLSID and unicode array:
CLSID clsid;
...
WCHAR * ostr;
HRESULT hr = StringFromCLSID(clsid, &ostr);
Program “Microsoft Visual Studio\Common\Tools\Guidgen.exe” is used to generate a guid. Choose the format of the guid and press button “Copy”. The generated guid will be in the clipboard.
You can also add it to the “Tools” menu with menu “Tools | Customize | Tools”.
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”.
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.
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.
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++.
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;
In C++ LPOLESTR is a 16-bit unicode string represented by const unsigned short *:
typedef OLECHAR __RPC_FAR * LPOLESTR;
typedef WCHAR OLECHAR;
In C++ LPOLESTR is a constant 16-bit unicode string represented by const unsigned short *:
typedef const OLECHAR __RPC_FAR *LPCOLESTR;
In C++ LPSTR is 8-bit multi-byte/ANSI string represented by char *
typedef CHAR *LPSTR, *PSTR;
typedef char CHAR;
In C++ LPCSTR is 8-bit multi-byte/ANSI string represented by const char *:
typedef CONST CHAR *LPCSTR, *PCSTR;
In C++ TCHAR is 8-bit character represented by char:
typedef char TCHAR, *PTCHAR;
#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);
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();
}
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.
BSTR bstr1 = SysAllocString(L"Hello world!");
OLECHAR * pOleStr = OLESTR("Good morning!");
BSTR bstr2 = SysAllocString(pOleStr);
SysReAllocString(&bstr1, L"Good morning!");
SysFreeString(bstr1);
CString cstr(“Hello!”);
BSTR bstr = cstr.AllocSysString();
BSTR bstr = SysAllocString(L"Hello Frank!");
CString cstr(bstr);
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);
char * mbstr = "Hello world!";
int len1 = strlen(mbstr);
wchar_t wcbuf[80];
int len2 = sizeof(wcbuf) / sizeof(wchar_t);
::memset(wcbuf, 0, sizeof(wcbuf));
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);
CP_ACP,
WC_COMPOSITECHECK,
wcstr,
len3,
mbbuf,
len4,
NULL,
NULL);
::MessageBox(NULL, mbbuf, NULL, MB_OK);
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 |
MessageBox(NULL, W2A(bstr), NULL, MB_OK);
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>.
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.
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.
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.
Create a new header file in any name, say iid.h. Use guid generator guidgen.exe to directly generate new guids in correct format for all new interfaces, and assign a constant alias for each guid:
//**************** iid.h *********************
// {D427CA52-AF28-40a4-A5C2-97EA029DCD0F}
DEFINE_GUID(IID_IRegistration,
0xd427ca52, 0xaf28, 0x40a4, 0xa5, 0xc2, 0x97, 0xea, 0x2, 0x9d, 0xcd, 0xf);
// {D518B0BF-3EE1-4976-9B6A-9F3443A2A186}
DEFINE_GUID(IID_IStatus,
0xd518b0bf, 0x3ee1, 0x4976, 0x9b, 0x6a, 0x9f, 0x34, 0x43, 0xa2, 0xa1, 0x86);
// {2F481E63-C189-4d99-A705-9F3F2DFB7145}
DEFINE_GUID(CLSID_Car,
0x2f481e63, 0xc189, 0x4d99, 0xa7, 0x5, 0x9f, 0x3f, 0x2d, 0xfb, 0x71, 0x45);
Create a corresponding source file iid.cpp. Simply include the following lines in the file:
//***************** iid.cpp *********************
#include "stdafx.h"
#include <windows.h>
#include <objbase.h> // Needed if WIN32_LEAN_AND_MEAN defined in stdafx.h
#include <initguid.h> // contains definition of DEFINE_GUID
#include "iid.h"
Header file <initguid.h> should be included before "iid.h". By putting the lines shown above into a cpp file and letting the client programming inserting this cpp file into his project, this precedence is guaranteed.
Note that when a client invokes for an IID or CLSID from the COM run time, the program will first loop up the guid definition file (iid.h in this case) to acquire the actual guid. That is to say, it is "0xd427ca52, 0xaf28, 0x40a4, 0xa5, 0xc2, 0x97, 0xea, 0x2, 0x9d, 0xcd, 0xf" not "IID_IRegistration" which is passed to the COM run time to be used to search in the System Registry. This is how the guid helps to maintain the global uniquenss of a coclass or interface.
//**************** 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:
char m_pcOwner[80];
int m_nSpeed;
};
//****************** Car.cpp ************************
#include "Car.h"
#include "iid.h"
extern ULONG g_lockCount;
extern ULONG g_objCount;
Car::Car()
{
m_refCount = 0;
g_objCount++;
::memset(m_pcOwner, 0, 80);
}
Car::~Car()
{
g_objCount--;
}
STDMETHODIMP_(ULONG) Car::AddRef()
{
return ++m_refCount;
}
STDMETHODIMP_(ULONG) Car::Release()
{
if(--m_refCount == 0)
{
delete this;
return 0;
}
else
return m_refCount;
}
STDMETHODIMP Car::QueryInterface(REFIID riid, void ** ppAny)
{
// IID_IUnknown is the REFIID of standard interface IUnknown
if(riid == IID_IUnknown)
{
// to avoid confusion caused by virtual inheritance
*ppAny = (IUnknown *)(IStatus *)this;
}
else if(riid == IID_IRegistration)
{
*ppAny = (IRegistration *)this;
}
else if(riid == IID_IStatus)
{
*ppAny = (IStatus *)this;
}
else
{
*ppAny = NULL;
return E_NOINTERFACE;
}
((IUnknown *)(*ppAny))->AddRef();
return S_OK;
}
STDMETHODIMP Car::GetOwner(BSTR * pBstrOwner)
{
mbstowcs(*pBstrOwner, m_pcOwner, 80);
return S_OK;
}
STDMETHODIMP Car::SetOwner(BSTR bstrOwner)
{
wcstombs(m_pcOwner, bstrOwner, 80);
return S_OK;
}
STDMETHODIMP Car::GetSpeed(int * pnSpeed)
{
*pnSpeed = m_nSpeed;
return S_OK;
}
STDMETHODIMP Car::SetSpeed(int nSpeed)
{
m_nSpeed = nSpeed;
return S_OK;
}
Macro STDMETHODIMP and STDMETHODIMP_(Type) are used to convert the function into __stdcall calling convention:
#define STDMETHODIMP HRESULT STDMETHODCALLTYPE
#define STDMETHODIMP_(type) type STDMETHODCALLTYPE
Pay attention the cascaded if structure in the QueryInterface method. In iid.h, we stored some guids and assigned an alias for each of them. But actually nothing linked one guid/ alias to one interface. Now here we find the link.
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;
}
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.
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;
}
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:
DllGetClassObject @1 PRIVATE
DllCanUnloadNow @2 PRIVATE
Create a new reg file say CarDll.reg to merge the server information such as the GUIDs and the location into the System Registry:
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.
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.
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.
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:
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// 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,
®ID);
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);
}
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.
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.
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.
To register and unregister a class object CoCarClassFactory to the running object table:
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
if(strstr(lpCmdLine, "/Embedding") || strstr(lpCmdLine, "-Embedding"))
{
CoCarClassFactory carClassFactory;
DWORD regID = 0;
CoRegisterClassObject(CLSID_CoCar,
(IClassFactory*)&carClassFactory,
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE, ®ID);
MSG ms;
while(GetMessage(&ms, 0, 0, 0))
{
TranslateMessage(&ms);
DispatchMessage(&ms);
}
CoRevokeClassObject(regID);
}
else
{
// Work as a normal application
}
CoUninitialize();
return 0;
}
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:
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(
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;
}
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);
HRESULT hr = CoCreateInstance(
CLSID_Any,
NULL,
CLSCTX_INPROC_SERVER,
IID_IAny,
if(FAILED(hr))
printf("CoCreateInstance failed!");
return 0;
}
There are four execution contexts you can use when calling CoCreateInstance or CoCreateInstanceEx:
CLSCTX_LOCAL_SERVER
CLSCTX_REMOTE_SERVER
CLSCTX_SERVER is an OR of the three server contexts.
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.
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.
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".
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.
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
{
};
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.
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.
[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.
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.
Suppose the IDL file name is Shapes.idl, the files generated by MIDL are:
1. Shapes.h Interface definition
2. Shapes.tlb binary type library
3. Shapes_i.c definition of all guids
4. Shapes_p.c and dlldate.c used to build proxy and stub DLLs for remote accessing
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
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.
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"
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.
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.
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.
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 ans as Integer
ans = c.Add(111, 222)
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).
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.
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.
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);
...
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;
};
};
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;
}
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);
}
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;
};
};
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
}
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;
}
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);
}
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.
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.
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.
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.
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.
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.
Determines how often and how strict does the COM run time check for the server the identification of the client.
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.
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.
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;
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:
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;
COAUTHINFO __RPC_FAR *pAuthInfo;
DWORD dwReserved2;
} COSERVERINFO;
The array of structure MULTI_QI hold the IIDs of the wanted interfaces, the returned IUnknown pointers and HRESULTs:
typedef struct tagMULTI_QI
{
const IID __RPC_FAR *pIID;
IUnknown __RPC_FAR *pItf;
} MULTI_QI;
ATL uses macro STDMETHOD to define a method. It is defined as:
#define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method
#define STDMETHODCALLTYPE __stdcall
Therefore,
STDMETHOD(Calculate)(int x, float y)
is in fact
virtual RESULT __stdcall Calculate(int x, float y)
STDMETHODIMP is used to mark the implementation of a method. It is defined as:
#define STDMETHODIMP HRESULT STDMETHODCALLTYPE
In AppWizard, choose ATL COM AppWizard and DLL option. It will create those entry points for the project. No coclass is generated yet.
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.
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”.
The skeletons of the methods have already been set up by the wizard.
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.
Let the coclass inherit from the new interfaces too. Add the COM_INTERFACE_ENTRY for the new interfaces in the COM_MAP.
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.
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.
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.
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:
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.
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
public:
};
//************** 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.
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.
In non-COm C++ environment, different threads in the same process can freely talk to each other. Objects that are exposed to multiple threads have to make all their data thread-safe if necessary.
Following is a multithreaded example making use of CRITICAL_SECTION to guarantee that a certain piece of code can only be accessed by one thread. The main thread creates a"ThreadParam" structure to hold parameters to pass to the new threads it fires. The parameters include a class object and a function pointer.
#include "stdafx.h"
#include "iostream.h"
#include <stdio.h>
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)
{
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;
}
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].
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.
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.
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?
When a coclass registers itself with *.rgs file in the system registery, it can register itself as one of four threading model by adding (or not adding) a ThreadingModel entry under HKCR\CLSID\<clsid>\InprocServer32:
1. Primary STA no ThreadingModel entry,
2. STA ThreadingModel = "Apartment",
3. MTA ThreadingModel = "Free",
4. Both ThreadingModel = "Both",
When you create a coclass (not a DLL or EXE housing) using ATL COM Wizard, you will be offered a chance to select from the above four options. Then the wizard will automatically generate a rgs file containing the correct registry entry.
Note that although a coclass can be dealt with by COM run time in the above four different ways, the coclass itself can be implemented in only two ways: STA or MTA. When using ATL it is determined by inheriting from template class CComObjectRootEx<CComSingleThreadModel> or CComObjectRootEx<CComMultiThreadModel>.
Especially, if you choose "Both" option, your component must aggregate with the free-threaded marshaller provided by COM. To do it, tick the "Free-threaded marshaller" option when you create an ATL object with the ATL Object Wizard.
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.
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.
ATL's support for thread protection i.e. synchronization at the end comes to four methods: Increment and Decrement which are used to increment and decrement the object reference count m_refCount, and Lock and Unlock which wraps proected code and extends to critical section function calls.
An ATL coclass always inherits from CComObjectRootEx<>, which takes one threading model class as template parameter, which can either be CComSingleThreadModel (for STA coclass) or CComMultiThreadModel (for MTA coclass). These two classes has exactly the same interfaces. They contain method Increment and Decrement, and the typedef of three other classes which contain method Lock and Unlock.
CComObjectRootEx<> itself provides four thread protection methods: InternalAddRef, InternalRelease, Lock, Unlock, which all turn around and call methods of the passed threading model class to deal with threading issues. Method InternalAddRef and InternalRelease are called by the CComObject<> template's IUnknown method AddRef and Release, and Lock and Unlock are used to enter and leave critical sections.
InternalQueryInterface is provided elsewhere by CComObjectRootBase.
template <class ThreadModel>
class CComObjectRootEx : public CComObjectRootBase
{
public:
typedef ThreadModel _ThreadModel;
typedef _ThreadModel::AutoCriticalSection _CritSec;
typedef CComObjectLockT<_ThreadModel> ObjectLock;
ULONG InternalAddRef()
{
ATLASSERT(m_dwRef != -1L);
return _ThreadModel::Increment(&m_dwRef);
}
ULONG InternalRelease()
{
ATLASSERT(m_dwRef > 0);
return _ThreadModel::Decrement(&m_dwRef);
}
void Lock() {m_critsec.Lock();}
void Unlock() {m_critsec.Unlock();}
private:
_CritSec m_critsec;
};
Because the two classes has the same interfaces, if you want to change the threading model of an existing component, all you need to do is simply swapping between CComSingleThreadModel and CComMultiThreadModel.
In class CComSingleThreadModel which is used for STA components, method Increment and Decrement simply use "++" or "- -" operation on the object reference count, and it typedefs CComFakeCriticalSection as the classes providing Lock and Unlock, which does nothing:
class CComSingleThreadModel
{
public:
static ULONG WINAPI Increment(LPLONG p) {return ++(*p);}
static ULONG WINAPI Decrement(LPLONG p) {return --(*p);}
typedef CComFakeCriticalSection AutoCriticalSection;
typedef CComFakeCriticalSection CriticalSection;
typedef CComSingleThreadModel ThreadModelNoCS;
};
class CComFakeCriticalSection
{
public:
void Lock() {}
void Unlock() {}
void Init() {}
void Term() {}
};
Because CComSingleThreadModel actually provides no useful thread-protecting code at all, you have to use cirtical section functions to protect your global and static data.
In comparison, in class CComMultiThreadModel which is used for MTA components, method Increment and Decrement uses API function InterlockedIncrement and InterlockedDecrement to safely operate on data member m_refCount, and it typedefs CComAutoCriticalSection to provide method Lock and Unlock, which extends to critical section function calls:
class CComMultiThreadModel
{
public:
static ULONG WINAPI Increment(LPLONG p) {return InterlockedIncrement(p);}
static ULONG WINAPI Decrement(LPLONG p) {return InterlockedDecrement(p);}
typedef CComAutoCriticalSection AutoCriticalSection;
typedef CComCriticalSection CriticalSection;
typedef CComMultiThreadModelNoCS ThreadModelNoCS;
};
class CComAutoCriticalSection
{
public:
void Lock() {EnterCriticalSection(&m_sec);}
void Unlock() {LeaveCriticalSection(&m_sec);}
CComAutoCriticalSection() {InitializeCriticalSection(&m_sec);}
~CComAutoCriticalSection() {DeleteCriticalSection(&m_sec);}
CRITICAL_SECTION m_sec;
};
To protect other data in the coclass, simply call method Lock and Unlock.
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.
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 *)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;
}
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>.
The coclass you create with COM AppWizard does not contain the implementation of IUnknown methods. Therefore they are abstract classes. They can not be directly created. Instead a coclass is passed to one of a set of template classes, which inherits from the coclass passed as template parameter, and provides a specific implementation of the IUnknown methods. This template classes become the real creatable coclasses.
The reason behind these logic is to separate the custom implementation of coclasses from the implementation of IUnknown, so that the ATL coclasses are more portable.
The definition of the most commonly used template class CComObject<> is listed below:
template <class Base>
class CComObject : public Base
{
public:
typedef Base _BaseClass;
CComObject(void* = NULL)
{
_Module.Lock(); // _Module is the global CComModule
}
~CComObject() // Set refcount to 1 to protect destruction
{
m_dwRef = 1L;
FinalRelease();
_Module.DeleteNonAddRefThunk(_GetRawUnknown());
_Module.Unlock();
}
STDMETHOD_(ULONG, AddRef)()
{
return InternalAddRef();
}
STDMETHOD_(ULONG, Release)()
{
ULONG l = InternalRelease();
if (l == 0)
delete this;
return l;
}
STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
{
return _InternalQueryInterface(iid, ppvObject);
}
template <class Q>
HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)
{
return QueryInterface(__uuidof(Q), (void**)pp);
}
static HRESULT WINAPI CreateInstance(CComObject<Base>** pp);
};
template <class Base>
HRESULT WINAPI CComObject<Base>::CreateInstance(CComObject<Base>** pp)
{
ATLASSERT(pp != NULL);
HRESULT hRes = E_OUTOFMEMORY;
CComObject<Base>* p = NULL;
ATLTRY(p = new CComObject<Base>())
if (p != NULL)
{
p->SetVoid(NULL);
p->InternalFinalConstructAddRef();
hRes = p->FinalConstruct();
p->InternalFinalConstructRelease();
if (hRes != S_OK)
{
delete p;
p = NULL;
}
}
*pp = p;
return hRes;
}
ATL’s COM map macros make use of those CComObject<> templates to create real coclass instances, so you normally do not need to directly deal with them. However, if you need to create an instance of an ATL coclass which is defined within your project (in such case you do not need to go through COM run time by calling CoCreateInstance), you need to use one of these template classes manually:
CComObject<CAnyCoclass> * pAny = NULL;
CComObject<CAnyCoclass>::CreateInstance(&pAny);
The template classes are:
1. CComObject<> non-aggregated, heap-based, affects the server’s object count;
2. CComObjectNoLock<> non-aggregated, does not adjust the server’s object count;
3. CComAggObject<> can work as aggregate;
4. CComObjectStack<> stack-based;
5. CComObjectGlobal<> life time depends on the life time of the server;
6. CComPolyObject<> may or may not work as aggregate;
7. CComTearOffObject<> implements a tear-off interface.
The hierarchy of an ATL coclass looks like:
class ATL_NO_VTABLE CAny :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CAny, &CLSID_Any>,
public IDispatchImpl<IAny, &IID_IAny, &LIBID_TESTLib>
{
...
As we have discussed before, template class CComObjectRootEx is responsible for providing threading support Increment, Decrement, Lock and Unlock. It inherits from class CComObjectRootBase, which provides the following functionality:
In <atldef.h>, there are following lines:
#ifdef _ATL_DISABLE_NO_VTABLE
#define ATL_NO_VTABLE
#else
#define ATL_NO_VTABLE __declspec(novtable)
#endif
If you do define _ATL_DISABLE_NO_VTABLE, macro _ATL_NO_VTABLE will extend to nothing. Otherwise it will extend to declarator __declspec(novtable), which stops the compiler from generating code to initialize the vPtr in the constructor(s) and destructor of the class. In many cases, this removes the only references to the vtable that are associated with the class and, thus, the linker will remove it. This can result in a significant reduction in code size. Only the most derived class needs vTable. ATL coclasses are not most derived classes, so they don’t need a vTable.
After the constructor call, you will have a vTable to use.
However, without a vtable, you can not call any virtual methods in your constructor. That’s why CComObjectRootBase provides method FinalConstruct and FinalRelease, in which you can call virtual methods and do initialization and clean up. The framework will call them after the instance is constructed or before it is destroyed.
CComObjectRootBase maintains a reference count m_dwRef for the coclass. It also provides method InternalQueryInterface, which turns around and calls AtlInternalQueryInterface, which iterates over the COM map and searches for an interface.
The abstract ATL coclass indirectly inherits from CComObjectRootBase, and is passed into and inherited by the template classes such as CComObject<>. The template class can then call InternalQueryInterface in its implementation of IUnknown::QueryInterface. InternalAddRef and InternalRelease are provided by the CComObjectRootEx<> itself.
CComObjectRootBase provides method OuterAddRef, OuterRelease and OuterQueryInterface and a public IUnknown pointer m_pOuterUnknown.
CComObjectRootBase provides a set of internal helper functions so that COM map macros can call to calculate vPtr for a given interface, including _Chain, _Break, _NoInterface, _Cache, _Delegate and _Creator.
The other template class which the ATL coclass inherits from is CComCoClass<>. It provides mainly three services:
1. It contains a call to macro DECLARE_CLASSFACTORY, which extends to typedef CComClassFactory to by your default class factory.
2. It invokes macro DECLARE_AGGREGATABLE to instruct ATL framework how the colcass should function – as a stand-alone or aggregated object.
3. It provides a number of overloaded Error methods to return error information to the client.
As you can see from chapter “Polymorphism” section “Late Binding with vtable and vPtr” in my C++ tutorial, because interfaces do not have data members, in a COM component’s memory footprint, the vPtrs of all interfaces that this component implements are placed one next to another. Each vPtr points to a vtable of one interface, whose function pointers points to the latest/lowest implementations of the interface’s methods. Returning a reference of an interface to the client is all about returning the vPtr of the interface to the client.
An ATL COM map provides a static array of _ATL_INTMAP_ENTRY structures, which stores the IIDs and vPtrs of all the interfaces implemented by the coclass. It also provides some simple functions that can be called to acquire from the array the vPtr of an interface given its IID.
An ATL COM map in a coclass definition looks like
class ATL_NO_VTABLE CSource :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CSource, &CLSID_Source>,
public IDispatchImpl<ISource, &IID_ISource, &LIBID_THREADSLib>
{
public:
CSource()
{}
DECLARE_REGISTRY_RESOURCEID(IDR_SOURCE)
DECLARE_PROTECT_FINAL_CONSTRUCT()
COM_INTERFACE_ENTRY(IDispatch)
// ISource
public:
STDMETHOD(Advise)(/*[in]*/ ICallBack * pCallBack);
STDMETHOD(DoWork)();
private:
ICallBack * m_pCallBack;
};
The above four macros will expend to the following lines:
//****************** BEGIN_COM_MAP macro *****************
public:
typedef CSource _ComMapClass;
static HRESULT WINAPI _Cache
(void* pv, REFIID iid, void** ppvObject, DWORD dw)
{
_ComMapClass* p = (_ComMapClass*)pv;
p->Lock();
HRESULT hRes = CComObjectRootBase::_Cache(pv, iid, ppvObject, dw);
p->Unlock();
return hRes;
}
IUnknown* _GetRawUnknown()
{
ATLASSERT(_GetEntries()[0].pFunc == _ATL_SIMPLEMAPENTRY);
return (IUnknown*)((int)this + _GetEntries()->dw);
}
IUnknown* GetUnknown()
{
return _GetRawUnknown();
}
HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject)
{
return InternalQueryInterface(this, _GetEntries(), iid, ppvObject);
}
const static _ATL_INTMAP_ENTRY * WINAPI _GetEntries()
{
static const _ATL_INTMAP_ENTRY _entries[] =
{
//*********** COM_INTERFACE_ENTRY(ISource) *************
{
// _ComMapClass is defined to be CSource
&__uuidof(ISource),
offsetofclass(ISource, _ComMapClass),
_ATL_SIMPLEMAPENTRY
},
//*********** COM_INTERFACE_ENTRY(IDispatch) *************
{
&__uuidof(IDispatch),
offsetofclass(IDispatch, _ComMapClass),
_ATL_SIMPLEMAPENTRY
},
//*********** END_COM_MAP macro *************
{
}
};
return _entries;
}
The structure is defined as follow:
struct _ATL_INTMAP_ENTRY
{
const IID * piid; // the interface id (IID)
DWORD dw;
_ATL_CREATORARGFUNC * pFunc; //NULL:end, 1:offset, n:ptr
};
piid is the IID of the interface, which is calculated in the macro by keyword __uuidof, which can retrieve the uuid attached to a type definition.
dw is the offset to the vPtr of a given interface in relate to the coclass’s this pointer, which is calculated in the macro by another macro offsetofclass.
pFunc can either be set to _ATL_SIMPLECOMENTRY or points to one of the CComObjectRootBase helper functions such as _Creator or _Cache. If pFunc is _ATL_SIMPLECOMENTRY, it tells AtlInternalQueryInterface that this interface is an "internal" interface directly inherited by the cocloass, not an aggregated or tear-off interface. Therefore its address can be calculated by the coclass's this pointer plus the provided offset dw:
if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset
{
...
IUnknown* pUnk = (IUnknown*)((int)pThis + pEntries->dw);
If pFunc is not _ATL_SIMPLECOMENTRY, it tells AtlInternalQueryInterface that the interface is an aggregation or tear-off interface. Than pFunc is supposed to point to one of the CComObjectRootBase helper functions, and AtlInternalQueryInterface calls that function through pFunc to calculate the interface address:
HRESULT hRes = pEntries->pFunc(pThis, iid, ppvObject, pEntries->dw);
Because all interfaces inherit from IUnknown, the vtables of all interface contains the function pointers of the three IUnknown methods. All of these function pointers points to the same latest/lowest implementations of the three methods. Therefore, you can access all three methods of IUnknown with the vPtr of any interface.
When the client asks for the IUnknown interface of the coclass, AtlInternalQueryInterface can simply get from the array the vPtr of any internal interface and return it to the client. So it chooses the first one in the map. All AtlInternalQueryInterface needs to do is to make sure that this interface is an internal interface, not an aggregated or tear-off interface, by checking that pEntries->pFunc is equal to _ATL_SIMPLECOMENTRY. From the implementation of AtlInternalQueryInterface you can see, the very first thing it does is to assert the above condition, and if it fails, the whole method fails.
For this reason, you must make sure that the first entry in
th COM map is represented by one of the four macros, which are all used to
declare internal interfaces and all specify pFunc to be _ATL_SIMPLECOMENTRY:
1. COM_INTERFACE_ENTRY
2. COM_INTERFACE_ENTRY2
3. COM_INTERFACE_ENTRY_IID
4. COM_INTERFACE_ENTRY2_IID
An ATL coclass does not provide implementation of IUnknown interface. Instead, a CComObject<> template classe takes the coclass as template parameter and inherits from it, and provides the three IUnknown methods. These methods simply turn around and call the InternalAddRef, InternalRelease and _InternalQueryInterface.
InternalAddRef and InternalRelease are provided by CComObjectRootEx<>, which turn around and call the Increment and Decrement method provided by the threading model class passed as template parameter.
_InternalQueryInterface is provided by the above-shown COM map macro, which turns around and calls InternalQueryInterface provided by CComObjectRootBase, which again turns around and calls ATL API AtlInternalQueryInterface, which makes use of the COM map and searches for an interface with an IID:
ATLINLINE ATLAPI AtlInternalQueryInterface(
void * pThis, // "this" pointer of the coclass
const _ATL_INTMAP_ENTRY * pEntries, // The array
REFIID iid, // Interface IID
void ** ppvObject) // Receiving interface pointer
{
// First entry in the com map should be a simple map entry
ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);
if (ppvObject == NULL) // No receiving pointer is passed
return E_POINTER;
*ppvObject = NULL;
if (InlineIsEqualUnknown(iid)) // use the first interface
{
IUnknown * pUnk = (IUnknown *)((int)pThis + pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
while (pEntries->pFunc != NULL)
{
BOOL bBlind = (pEntries->piid == NULL);
if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid))
{
if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) // offset
{
ATLASSERT(!bBlind);
IUnknown* pUnk = (IUnknown*)((int)pThis + pEntries->dw);
pUnk->AddRef();
*ppvObject = pUnk;
return S_OK;
}
else //actual function call
{
pEntries->pFunc(pThis, iid, ppvObject, pEntries->dw);
if (hRes == S_OK || (!bBlind && FAILED(hRes)))
return hRes;
}
}
pEntries++;
}
return E_NOINTERFACE;
}
Suppose we have two interfaces IFirst and ISecond, each with a method Hi. As you can see from section “Method Name Clash and Ambiguity in Multiple Inheritance” in chapter “Multiple Inheritance” in my C++ tutorial, if we want to have to different implementations of method Hi, we must have two wrapper classes implementing the two interfaces, and let the coclass inherit from these two classes.
The two wrapper classes only need to implement the clashing method Hi (therefore they are still abstract classes), leaving non-clashing methods (MethodFirst and MethodSecond) to be implemented by the coclass as usual:
// **************** Test.idl ******************
...
interface IFirst : IUnknown
{
[helpstring("method Hi")] HRESULT Hi();
[helpstring("method MethodFirst")] HRESULT MethodFirst();
};
...
interface ISecond : IUnknown
{
[helpstring("method Hi")] HRESULT Hi();
[helpstring("method MethodSecond")] HRESULT MethodSecond();
};
...
// ************* Impls.h *******************
struct IFirstImpl : public IFirst {
STDMETHODIMP Hi()
{
m("Hi from IFirstImpl!");
return S_OK;
}
};
struct ISecondImpl : public ISecond {
STDMETHODIMP Hi()
{
m("Hi from ISecondImpl!");
return S_OK;
}
};
// ************ CAny.h ***************
#ifndef __ANY_H_
#define __ANY_H_
#include "resource.h" // main symbols
#include "impls.h"
class ATL_NO_VTABLE CAny :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CAny, &CLSID_Any>,
public IFirstImpl,
public ISecondImpl
{
public:
CAny()
{}
DECLARE_REGISTRY_RESOURCEID(IDR_ANY)
DECLARE_NOT_AGGREGATABLE(CAny)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CAny)
COM_INTERFACE_ENTRY(IFirst)
COM_INTERFACE_ENTRY(ISecond)
END_COM_MAP()
public:
STDMETHOD(MethodSecond)();
STDMETHOD(MethodFirst)();
//STDMETHOD(Hi)(); // Do not implement!
};
#endif //__ANY_H_
The COM map still gives the client the vPtrs of the two interfaces, not that of the two wrapper classes. Actually you can put the name of the wrapper classes instead of the interfaces in the COM map, so that it gives the client the vPtrs of the wrapper classes. It doesn’t matter, because the function pointers of method Hi in the vtables in both the interface and its wrapper class point to the implementation in the wrapper class. But to use the second option you have to give the IID of the interfacess to their wrapper classes.
Suppose we have two interfaces IAquatic and IMammal which inherit from interface ICreature. Coclass CDelphin implements all three interfaces: ICreature, IAquatic and IMammal. This coclass in ATL will look like
class ATL_NO_VTABLE CDelphin :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CDelphin, &CLSID_Delphin>,
public IAquatic, // Instead of IInterf1
public IMammal // Instead of IInterf2
{
public:
CDelphin()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_DELPHIN)
DECLARE_NOT_AGGREGATABLE(CDelphin)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CDelphin)
COM_INTERFACE_ENTRY(ICreature) // Ambiguious!
COM_INTERFACE_ENTRY(IAquatic)
COM_INTERFACE_ENTRY(IMammal)
};
When compiler casts the coclass’s this pointer to ICreature, it sees two paths, so it will be confused. We have to let the compiler know which path to take. Now suppose we want to take IMammal’s path, we can use one of the following three macros to substitute the basic COM_INTERFACE_ENTRY:
COM_INTERFACE_ENTRY2(ICreature, IMammal) // or
COM_INTERFACE_ENTRY_IID(IID_ICreature, IMammal) // or
COM_INTERFACE_ENTRY2_IID(IID_ICreature, ICreature, IMammal)
Because of the nature of COM – once a coclass supports an interface it must forever support it, with the evolution of the server, there must be some interfaces which are old and have very little chance to be used. If a coclass implements lots of such old interfaces, it will carry some performance and memory overhead – it takes time and memory to create the vPtrs of the old interfaces.
For each of these old interfaces, we can create a wrapper class to implement it separately from the coclass. The coclass no longer implement the old interface. In its QueryInterface, when this old interface is asked, we simply create a new instance of the wrapper class, cast it and return it to the client. Such an “stand-alone” interface is called tear-off interface. The coclass is called owner class.
The wrapper class has the same reference count m_refCount and the same implementation of AddRef and Release as the coclass, so that it can delete itself when not referenced anymore. It holds a pointer to the owner class, so that in its own QueryInterface it can call the owner’s QueryInterface to direct the queries back to the owner.
// ****************** Base.h *********************
#include "unknwn.h"
#include <windows.h>
struct IPopular : public IUnknown {
STDMETHOD(Hi)() PURE;
};
struct IOld : public IUnknown {
STDMETHOD(Hello)() PURE;
};
// ******************* Old.h **********************
#include "stdafx.h"
#include "base.h"
#include "owner.h"
class COld : public IOld {
private:
public:
COld(COwner * pOwner);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);
STDMETHODIMP Hello();
};
//******************* Old.cpp **********************
#include "stdafx.h"
#include "Old.h"
#include "iid.h"
COld::COld(COwner * pOwner) : m_refCount(0), m_pOwner(pOwner) {}
STDMETHODIMP_(ULONG) COld::AddRef()
{
return ++m_refCount;
}
STDMETHODIMP_(ULONG) COld::Release()
{
if(--m_refCount == 0)
{
delete this;
return 0;
}
else
return m_refCount;
}
STDMETHODIMP COld::QueryInterface(REFIID riid, void ** ppv)
{
if(riid == IID_IOld)
*ppv = (IOld *)this;
else
return m_pOwner->QueryInterface(riid, ppv);
return S_OK;
}
STDMETHODIMP COld::Hello()
{
printf("Hello from COld!\n");
return S_OK;
}
//**************** Owner.h ******************
#include "stdafx.h"
#include "base.h"
class COwner : public IPopular {
public:
COwner();
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);
STDMETHODIMP Hi();
private:
ULONG m_refCount;
};
//****************** Owner.cpp *****************
#include "stdafx.h"
#include "owner.h"
#include "Old.h"
#include "iid.h"
COwner::COwner() : m_refCount(0)
{}
STDMETHODIMP_(ULONG) COwner::AddRef()
{
return ++m_refCount;
}
STDMETHODIMP_(ULONG) COwner::Release()
{
if(--m_refCount == 0)
{
delete this;
return 0;
}
else
return m_refCount;
}
STDMETHODIMP COwner::QueryInterface(REFIID riid, void ** ppv)
{
if(riid == IID_IUnknown)
*ppv = (IUnknown *)this;
else if(riid == IID_IPopular)
*ppv = (IPopular *)this;
else if(riid == IID_IOld)
*ppv = (IOld *)new COld(this);
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
STDMETHODIMP COwner::Hi()
{
printf("Hi from COwner!\n");
return S_OK;
}
//******************* main *********************
int main(int argc, char* argv[])
{
HRESULT hr = pOwner->QueryInterface(IID_IPopular, (void **)&pPopular);
pPopular->Hi();
// Querying the old interface from the polular one
hr = pPopular->QueryInterface(IID_IOld, (void **)&pOld);
pOld->Hello();
// Querying the popular interface from the old one
hr = pOld->QueryInterface(IID_IPopular, (void **)&pPopular);
pPopular->Hi();
return 0;
}
Output will be:
Hello from COld!
Hi from COwner!
In ATL, class CComTearOffObjectBase is provided to be used as a base class of a tear-off class. It inherits from CComObjectRootEx to provide synchronization support to the tear-off class, and added the needed pointer back to the owner class:
template <class Owner, class ThreadModel = CComObjectThreadModel>
class CComTearOffObjectBase : public CComObjectRootEx<ThreadModel>
{
public:
typedef Owner _OwnerClass;
CComTearOffObjectBase() {m_pOwner = NULL;}
};
An ATL tear-off class inherits from CComTearOffObjectBase and implement the tear-off interface. It looks quite like a normal coclass except that it does not inherit from CComCoClass, which provides class factory and aggregation support:
class COwner;
class ATL_NO_VTABLE COld :
public CComTearOffObjectBase<COwner, CComSingleThreadModel>,
public IOld
{
public:
STDMETHOD(Hi)();
BEGIN_COM_MAP(COld)
};
An ATL owner class looks like:
class ATL_NO_VTABLE COwner :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<COwner, &CLSID_Popular>,
public IPopular
{
public:
COwner()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_POPULAR)
DECLARE_NOT_AGGREGATABLE(COwner)