Introduction
ATL/AUX Library is a set of VC++ helpers and patterns I develop through my experience with COM/ATL. The project was started for my own needs almost two years ago. It helps me to automate some routine coding tasks and concentrate on the problem itself. The library is free and evolving, welcome to use it and hope you find it helpful. To be notified about library updates and ATL development tricks, subscribe to the ATL/AUX Billboard. I'd appreciate any technical feedback there; subscription isn't required to post.
What's new
Most recent ATL/AUX version at CodeProject is 1.10.0015. For the most recent version, go here.
ATL/AUX Library Manual
Copyright (c) by Andrew Nosenko, 1997-2000.
Under Construction
I still need to have done detailed description for each macro/API/class, but hopefully I covered the most interesting ATL/AUX topics, having revised old stuff and added new ones. Let me know your opinion on it.
Overview Topics
About Error Handling and Diagnostics
The set of macros was developed to facilitate the COM code debugging process, simplify the code logic, and simultaneously improve its robustness and readability.
For example, during debug session, I've got the following error output at the DevStudio Debug Output Window:
D:\DevStudio\MyProjects\DAPlayer\Host.cpp(441) :
COM Error #800C0005 [DirectAnimation.DAView.1/IDAView: Importation failed:
Could not access the file
'D:\DevStudio\MyProjects\DAPlayer\examples\media\_cursor.gif'.]
I then click at the above line and automatically get to my source code line which caused the error:
_S( m_view->StartModel(img, sound, 0) );
The central part of error diagnostic is _HR(hr) macro. It takes an HRESULT
value and traces all available error information as the above sample shows. The formatting is done in a way similar to compiler errors output, which Developer Studio understands and can open the affected source file and position the cursor at the place where the error occurred. Having traced the error information, _HR(hr)
simply returns hr
, that could be further analyzed. With Release Build, _HR(hr)
simply expands as (hr
). _HR
macro may be used at any expression or statement wherever nice debug diagnostics is appropriate.
The common case in COM programming is the uniform HRESULT
function return type. I built _S(hr) macro on top of _HR
to check the passed HRESULT
value and return it if error is indicated. I prefer this approach as an alternative to C++ exception handling as it produces faster and smaller code. For convenience, there also is _R, _R_OK, _R_FAIL return macros:
#define _SUCCEEDED(exp) SUCCEEDED(_HR(exp))
#define _FAILED(exp) FAILED(_HR(exp))
#define _S(exp) { HRESULT $hr$ = (exp); if ( _FAILED($hr$) ) return $hr$; }
#define _R(exp) return _HR(exp)
#define _R_FALSE return S_FALSE
#define _R_OK return S_OK
#define _R_FAIL _R( E_FAIL )
_BP(hr) is another useful debugging macro based on _HR
. It hits breakpoint if hr
indicates an error.
Here are several (a bit strained) sketches of how to use the above macros:
- Simple method:
HRESULT CBar::Foo() {
IClassFactoryPtr class;
_S( CoGetClassObject(CLSID_Foo, CLSCTX_ALL,
class.GetIID(), (void**)&class) );
IFooPtr foo;
_S( class->CreateInstance(NULL, foo.GetIID(), (void**)&foo) );
if ( _HR(foo->IsFooReady()) == S_FALSE )return S_OK;
...
_R( foo->Foo() );
}
- C++ constructor:
CFoo::CFoo(HRESULT& hr) {
_S_VAR(hr, m_bar.CreateInstance(CLSID_Bar));
...
hr = S_OK;
}
- no
HRESULT
return, but have nice diagnostics:
if ( _FAILED(m_bar->Foo()) ) return;
- Break into debugger in case of an error. Then you can walk the stack, etc.
_BP( foo->Foo() );
Structuring code this way provides simple linear flow control. What's more important, it possesses more safe and robust code as there is no more assumptions on one or another call to succeed. Automation errors (set with SetErrorInfo) bubble up through the call chain and are available to the caller. If we use Smart Pointers, Auto Pointers, and other C++ objects that live on the stack frame relying on automatic destructor invocation, we may not be bothered to release acquired resources. Check out CAuto class to see how to reclaim acquired resources automatically.
Other error handing related macros: ASSERT_HR, VERIFY_HR, _FAILED, _R, _REPORT, _S_VAR, _SUCCEEDED.
To experiment, I rewrote ATL3 AtlGetObjectSourceInterface
API (from AtlCom.h). The API retrieves the default sources interface GUID on passed object:
struct CTLADtor {
ITypeLibPtr m_p;
template<class Ptr> CTLADtor(const Ptr& p): m_p(p) {}
void Destroy(TLIBATTR* v) { m_p->ReleaseTLibAttr(v); }
};
struct CTADtor {
ITypeInfoPtr m_p;
template<class Ptr> CTADtor(const Ptr& p): m_p(p) {}
void Destroy(TYPEATTR* v) { m_p->ReleaseTypeAttr(v); }
};
AUXAPI AuxGetObjectSourceInterface(IUnknown* punkObj, GUID* plibid,
IID* piid, unsigned short* pdwMajor, unsigned short* pdwMinor)
{
if ( punkObj == NULL ) _R_FAIL;
IDispatchPtr spDispatch;
_S( punkObj->QueryInterface(&spDispatch) );
ITypeInfoPtr spTypeInfo;
_S( spDispatch->GetTypeInfo(0, 0, &spTypeInfo) );
ITypeLibPtr spTypeLib;
_S( spTypeInfo->GetContainingTypeLib(&spTypeLib, 0) );
CAuto<TLIBATTR*, CTLADtor> plibAttr(spTypeLib);
_S( spTypeLib->GetLibAttr(&plibAttr) );
memcpy(plibid, &plibAttr->guid, sizeof(GUID));
*pdwMajor = plibAttr->wMajorVerNum;
*pdwMinor = plibAttr->wMinorVerNum;
IProvideClassInfo2Ptr spInfo2 = punkObj;
if ( spInfo2 ) {
_S( spInfo2->GetGUID(GUIDKIND_DEFAULT_SOURCE_DISP_IID, piid) )
_R_OK;
}
ITypeInfoPtr spInfoCoClass;
IProvideClassInfoPtr spInfo = punkObj;
if ( spInfo )
_S( spInfo->GetClassInfo(&spInfoCoClass) )
else
{
IPersistPtr spPersist;
_S( punkObj->QueryInterface(&spPersist) );
CLSID clsid;
_S( spPersist->GetClassID(&clsid) );
_S( spTypeLib->GetTypeInfoOfGuid(clsid, &spInfoCoClass) );
}
CAuto<TYPEATTR*, CTADtor> pAttr(spInfoCoClass);
_S( spInfoCoClass->GetTypeAttr(&pAttr) );
HREFTYPE hRef;
for ( int i = 0; i < pAttr->cImplTypes; i++ )
{
int nType;
_S( spInfoCoClass->GetImplTypeFlags(i, &nType) );
if ( nType == (IMPLTYPEFLAG_FDEFAULT | IMPLTYPEFLAG_FSOURCE) )
{
_S( spInfoCoClass->GetRefTypeOfImplType(i, &hRef) )
ITypeInfoPtr spInfo;
_S( spInfoCoClass->GetRefTypeInfo(hRef, &spInfo) );
CAuto<TYPEATTR*, CTADtor> pAttrIF(spInfo);
_S( spInfo->GetTypeAttr(&pAttrIF) );
memcpy(piid, &pAttrIF->guid, sizeof(GUID));
_R_OK;
}
}
_R_FAIL;
}
There is no extra branching, but rather comprehensive error diagnostics for every call. Note also how CAuto is used to release typelib structures. Just to compare, here is the original ATL3 code (for illustrative purpose only, copyright © by Microsoft Corp.):
ATLINLINE ATLAPI AtlGetObjectSourceInterface(IUnknown* punkObj,
GUID* plibid, IID* piid, unsigned short* pdwMajor,
unsigned short* pdwMinor)
{
HRESULT hr = E_FAIL;
if (punkObj != NULL)
{
CComPtr<IDispatch> spDispatch;
hr = punkObj->QueryInterface(IID_IDispatch, (void**)&spDispatch);
if (SUCCEEDED(hr))
{
CComPtr<ITypeInfo> spTypeInfo;
hr = spDispatch->GetTypeInfo(0, 0, &spTypeInfo);
if (SUCCEEDED(hr))
{
CComPtr<ITypeLib> spTypeLib;
hr = spTypeInfo->GetContainingTypeLib(&spTypeLib, 0);
if (SUCCEEDED(hr))
{
TLIBATTR* plibAttr;
hr = spTypeLib->GetLibAttr(&plibAttr);
if (SUCCEEDED(hr))
{
memcpy(plibid, &plibAttr->guid, sizeof(GUID));
*pdwMajor = plibAttr->wMajorVerNum;
*pdwMinor = plibAttr->wMinorVerNum;
spTypeLib->ReleaseTLibAttr(plibAttr);
CComPtr<IProvideClassInfo2> spInfo;
hr = punkObj->QueryInterface(IID_IProvideClassInfo2,
(void**)&spInfo);
if (SUCCEEDED(hr) && spInfo != NULL)
hr = spInfo->GetGUID(GUIDKIND_DEFAULT_SOURCE_DISP_IID, piid);
else
{
CComPtr<ITypeInfo> spInfoCoClass;
CComPtr<IPersist> spPersist;
CLSID clsid;
hr = punkObj->QueryInterface(IID_IPersist,
(void**)&spPersist);
if (SUCCEEDED(hr))
{
hr = spPersist->GetClassID(&clsid);
if (SUCCEEDED(hr))
{
hr = spTypeLib->GetTypeInfoOfGuid(clsid, &spInfoCoClass);
if (SUCCEEDED(hr))
{
TYPEATTR* pAttr=NULL;
spInfoCoClass->GetTypeAttr(&pAttr);
if (pAttr != NULL)
{
HREFTYPE hRef;
for (int i = 0; i < pAttr->cImplTypes; i++)
{
int nType;
hr = spInfoCoClass->GetImplTypeFlags(i, &nType);
if (SUCCEEDED(hr))
{
if (nType == (IMPLTYPEFLAG_FDEFAULT |
IMPLTYPEFLAG_FSOURCE))
{
hr =
spInfoCoClass->GetRefTypeOfImplType(i, &hRef);
if (SUCCEEDED(hr))
{
CComPtr<ITypeInfo> spInfo;
hr =
spInfoCoClass->GetRefTypeInfo(hRef, &spInfo);
if (SUCCEEDED(hr))
{
TYPEATTR* pAttrIF;
spInfo->GetTypeAttr(&pAttrIF);
if (pAttrIF != NULL)
{
memcpy(piid, &pAttrIF->guid, sizeof(GUID));
}
spInfo->ReleaseTypeAttr(pAttrIF);
}
}
break;
}
}
}
spInfoCoClass->ReleaseTypeAttr(pAttr);
}
}
}
}
}
}
}
}
}
}
return hr;
}
C++ Exception Handling
Here is the new AuxEH.h header, macros to handle C++ exception within COM interface method. C++ exceptions must not be thrown across a COM server method boundaries as the client doesn't expect to handle them. Rather, IErrorInfo might be set with SetErrorInfo and proper HRESULT
should be returned.
The idea is to easily handle _com_error, AFX CException and all other possible (...) exceptions with a single statement and have full diagnostic similar to _S and _HR macros. Just put the code that may throw in try { ... }
and append the block with either _CATCH_COM()
, _CATCH_AFX()
, _CATCH_ANY()
, any combination of them or _CATCH_ALL()
. Rich error information (IErrorInfo
) will be preserved and propagated to the outer caller. AFX exceptions are translated into IErrorInfo
within _CATCH_AFX()
block. _CATCH_ALL()
is defined to catch all possible exceptions and expanded as _CATCH_COM() _CATCH_AFX() _CATCH_ANY()
.
You may use _THROW
to throw _com_error
upon failed HRESULT
. It will look for IErrorInfo
set and take an ownership over it. From other hands, _SAFE
may be used to catch a possible _com_error
thrown by a #import
-generated high level wrapper; IErrorInfo
will be set (if any) and HRESULT
will be returned to the caller of your method. _SAFE(exp)
is a shorthand for try { exp; }
_CATCH_COM();
.
Dr. John F. Holliday proposed yet another useful macro: _ON_ERROR
, to handle both HRESULT
and exception-thrown errors with a single line, universally. Thanks John! I've modified it a bit by adding AFX support and $hr
(the error code). As John noted, _ON_ERROR
is a real time saver for finding subtle errors that only appear as exceptions; the downside is the amount of code that it generates. Examples:
_ON_ERROR(p->Test1(), return $hr);
_ON_ERROR(p->Test2(), return E_FAIL);
_ON_ERROR(p->Test3(), goto error);
AuxEH.h header should be included after AtlAux.h at the project's StdAfx.h. Here is a sample of how to use the above macros in ATL server that uses MFC:
STDMETHODIMP CTest::Init(IDispatch *obj1, IDispatch *obj2)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
_ON_ERROR(TestObject(obj1), return E_FAIL);
ITestPtr test(obj1);
_SAFE( test->AddObject(this) );
try {
if ( !obj2 ) _THROW( E_POINTER );
_THROW( raw_TestObject(obj2) );
ITestPtr test(obj2);
test->AddObject(this);
CFile(m_strFile, CFile::modeRead);
return S_OK;
}
_CATCH_ALL()
}
FYI, here is the great article on Compiler COM Support (_com_error
, #import
, etc:): Microsoft Visual C++ 5.0 Compiler Native COM Support.
Dynamic Creation of ATL Objects
The common way to create an ATL object by hand is the following:
CComObject<CFoo>* p;
_S( CComObject<CFoo>::CreateInstance(&p) );
Besides a lot of typing, we are limited to CComObject wrapper only (with ATL3, CreateInstance
is also available on CComAggObject and CComPolyObject). I made a simple templatized AuxCreateObject API to create an object the way defined by ATL:
template <class Base>
AUXAPI AuxCreateObject(Base** pp, void* pCtx = NULL) {
ASSERT(pp != NULL);
HRESULT hRes = E_OUTOFMEMORY;
Base* p = NULL;
ATLTRY(p = new Base())
if (p != NULL)
{
p->SetVoid(pCtx);
p->InternalFinalConstructAddRef();
hRes = p->FinalConstruct();
p->InternalFinalConstructRelease();
if (hRes != S_OK)
{
delete p;
p = NULL;
}
}
*pp = p;
return hRes;
}
One wonderful thing about AuxCreateObject
is that it takes an optional pCtx
parameter and passes it to the object during the construction. ATL prevents us from using non-default constructors (because there is derived CComObject
-family wrapper). The only way to pass an initialization information is to provide our own SetVoid(void* pCtx)
method, store passed pCtx
pointer, then use it at the FinalConstruct
call time. That's what AuxCreateObject
takes the second parameter for. Here is an example of the object being initialized this way:
class ATL_NO_VTABLE CHelper:
public CComObjectRoot,
public IHelper,
...
{
public:
CParent* m_pParent;
IDispatchPtr m_disp;
void SetVoid(void* pv) {
ASSERT(pv); m_pParent = (CParent*)pv;
}
HRESULT FinalConstruct() {
_R( m_pParent->GetDispatch(&m_disp) );
}
...
};
To construct such a CHelper
object, we use AuxCreateObject
. Take care, no AddRef
is done on CHelper
object, to act the same way as CComObject::CreateInstance
:
HRESULT CParent::CreateHelper(CHelper** pHelper) {
CComObject<CHelper>* p;
_S( AuxCreateObject(&p, this) );
*pHelper = p;
_R_OK;
}
Note, if FinalConstruct()
fails, the object is automatically destroyed and the failure code is returned by AuxCreateObject
.
The nearest purely ATL solution is to use ATL CComCreator<>::CreateInstance
method. This is not the same however; it returns the requested interface, not the object, and does AddRef
. CComCreator
is used by ATL class factories.
HRESULT CParent::CreateHelper(IHelper** helper) {
_R( CComCreator< CComObject< CHelper > >::CreateInstance(this,
IID_IHelper,(void**)helper) );
}
Note by the way, neither FinalConstruct
nor SetVoid
are virtual. ATL makes almost no use of virtual functions. It uses templates and C++ inheritance rules to get the job done. Look, basic FinalConstruct
and SetVoid
are defined at CComObjectRootBase
from which our object derives. At our object, we override these methods (but they are not virtual!), so CComObject<>
derives from ours and inherits our implementation. After all, CComCreator::CreateInstance
calls SetVoid
and FinalConstruct
which happen to be ours.
Note also, by definition, the ATL object that relies on initialization information set with SetVoid
cannot support the aggregation.
Of course, we always can split the initialization by two parts and provide separate method, say CHelper::Init(CParent* m_pParent)
, to carry out the initialization as a separate step.
Last note, all the above ways of construction may be too complex. For simple objects, we may use CUnkImpl<> class that allows non-default C++ constructors (because there is no object derived from ours).
FYI, Don Box discusses some aspects of COM object construction in his great column at Jul'97 MSJ.
Custom _ATL_MIN_CRT for Construction/Destruction of Global C++ Objects: AuxCrt.cpp
Standard _ATL_MIN_CRT
support does the job for eliminating the CRT overhead well. But unfortunately, it doesn't support the use of global C++ constructs, like the following:
class CTest {
public:
CTest() {
MessageBox(NULL, _T("Hello, I'm intitialized"),
_T("Static object"), MB_SETFOREGROUND | MB_OK);
}
~CTest() {
MessageBox(NULL, _T("Bye, I'm done"), _T("Static object"),
MB_SETFOREGROUND | MB_OK);
}
};
static CTest g_test;
The above would lead to linker conflicts, because CRT code for constructors/destructors invocation would be referenced. To overcome this, I've made AuxCrt.cpp, a small replacement of AtlImpl.cpp that provides required CRT functionality at the same cost (i.e., no extra overhead). Its use is simple:
- Do not specify
DllMain
as Entry Point in the project Linker/Output Options tab (or specify _DllMainCRTStartup
).
- Replace AtlImpl.cpp with AuxCrt.cpp at the StdAfx.cpp file:
#include "stdafx.h"
#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif
#include <AuxCrt.cpp> // includes AtlImpl.cpp inside
Dispinterface Event Sink Implementation: ISinkImpl<>
The idea is to handle source dispinterface events with properly laid out C++ virtual functions. I need to maintain some ATL2 projects, and have been working on ISinkImpl for a long time. ATL3 IDispEventImpl was a great help, still my approach is a bit different. I use an intermediate sink class that defines virtual functions to be overridden by a derived class. I believe it's more reusable, as in the long run, I have a collection of some favorite sinks. For example, here is the sink for default event sent of IE4 WebBrowser AX Control:
class ATL_NO_VTABLE DWebBrowserEvents2Sink:
public CSinkImpl<DWebBrowserEvents2Sink,
&DIID_DWebBrowserEvents2, &LIBID_SHDocVw>
{
STDMETHOD_(void, StatusTextChange)(BSTR Text) {}
STDMETHOD_(void, ProgressChange)(long Progress, long ProgressMax) {}
STDMETHOD_(void, CommandStateChange)(long Command, VARIANT_BOOL Enable) {}
STDMETHOD_(void, DownloadBegin)() {}
STDMETHOD_(void, DownloadComplete)() {}
STDMETHOD_(void, TitleChange)(BSTR Text) {}
STDMETHOD_(void, PropertyChange)(BSTR szProperty) {}
STDMETHOD_(void, BeforeNavigate2)(IDispatch* pDisp,
VARIANT * URL, VARIANT * Flags, VARIANT * TargetFrameName,
VARIANT * PostData, VARIANT * Headers,
VARIANT_BOOL * Cancel) {}
STDMETHOD_(void, NewWindow2)(IDispatch** ppDisp, VARIANT_BOOL * Cancel) {}
STDMETHOD_(void, NavigateComplete2)(IDispatch* pDisp, VARIANT * URL ) {}
STDMETHOD_(void, DocumentComplete)(IDispatch* pDisp, VARIANT * URL ) {}
STDMETHOD_(void, OnQuit)() {}
STDMETHOD_(void, OnVisible)(VARIANT_BOOL Visible) {}
STDMETHOD_(void, OnToolBar)(VARIANT_BOOL ToolBar) {}
STDMETHOD_(void, OnMenuBar)(VARIANT_BOOL MenuBar) {}
STDMETHOD_(void, OnStatusBar)(VARIANT_BOOL StatusBar) {}
STDMETHOD_(void, OnFullScreen)(VARIANT_BOOL FullScreen) {}
STDMETHOD_(void, OnTheaterMode)(VARIANT_BOOL TheaterMode) {}
};
At the sink-derived class, we need to override selected methods to provide the actual processing:
class CSink:
public CComObjectRoot,
public IUnknown,
public DWebBrowserEvents2Sink
{
public:
COM_MAP_NO_ENTRIES()
STDMETHOD_(void, StatusTextChange)(BSTR Text) {
Log(_T("StatusTextChange: %ls\n"), Text);
}
STDMETHOD_(void, OnQuit)() {
Log(_T("OnQuit\n"));
}
STDMETHOD_(void, NavigateComplete2)(IDispatch* pDisp, VARIANT * URL ) {
Log(_T("NavigateComplete2: %ls\n"), V_BSTR(URL));
}
STDMETHOD_(void, DocumentComplete)(IDispatch* pDisp, VARIANT * URL ) {
Log(_T("DocumentComplete: %ls\n"), V_BSTR(URL));
}
};
The best way to illustrate ISinkImpl<>
is by example, of course. I've put full working client side sample, the IE4 Automation Controller. It creates an instance of Internet Explorer Application, navigates it to MS site, and logs all events that it fires. The sample is available here: IEClient.zip [64.6 Kb]. The project is located at \IEClient folder of the ZIP; it should compile with both VC5 and VC6. All other ATL-related stuff is at \Shared folder. There, you might find another reusable class, CLogWindow
(AuxLog.h), used to log IE4 events in a separate window.
FYI, a good illustration of standard ATL3 IDispEventImpl approach could be found here: AtlEvnt.exe Creates ATL Sinks Using IDispEventImpl.
IIDs/Smart Pointers Generator
I'm quite used to VC5+ __uuidof()
extension (does anybody out there use COM/ATL with non-VC compiler? :) ). But I don't like to mix IID_<Name>
and __uuidof(<Name>)
styles. ATL is built the way it uses IID_* slyle
; I use smart pointers intensively, which poses __uuidof(*)
. Having both IID_IUnknown
and __uuidof(IUnknown)
would place both the same GUIDs at the final binary.
Another issue is the naming convenience. I use my own IPtr<>
, though you may be using standard CCom[QI]Ptr
(atlbase.h) or _com_ptr_t
(comdef.h). Whatever, it's boring to type IPtr<IMyLovelyIface>
or even worse IPtr<IMyLovelyIface
, &IID_IMyLovelyIface>
each time. I prefer IMyLovelyIfacePtr
instead. VC5 comdef.h (Compiler COM Support) file allows to define your own smart pointer class and provides smart pointer definitions like IUnknownPtr
for majority of COM interfaces. More, #import
directive can generate IMyLovelyIfacePtr
for custom typelib, e.g.:
#define _COM_SMARTPTR IPtr
#import "MyLovelyLib.tlb" \
raw_interfaces_only, \
named_guids, \
no_namespace
But what about tons of other interfaces, including all those new ones coming with frequently updated INetSDK/Platform SDK, etc.?
I've developed WSH (Windows Scripting Host) script, IDGen.js, to solve both of the above issues. Download ax_idgen.zip [1.38 Kb] and its output for the latest Platform SDK interfaces: ax_id.zip [38.6 Kb].
The files were built with the following command:
dumpbin /LINKERMEMBER Uuid.lib >ax cscript idgen.js ax
Look at the typical StdAfx.h file that uses ATL/AUX. This way, all IID_*
get redefined as __uuidof(*)
before any of them might get referenced by ATL. Note that you can include ax_id.h several times, as you add new MIDL-generated SDK headers.
[Note: the following section was originally written for VC5. I'll update it for VC6 soon.] Besides standard interfaces, I use the Generator for my own IDL files. To automate the thing, I use it as part of the build process. To try this, select Settings for your IDL, then add at Custom Build:
Build commands
- midl $(InputPath)
- cscript ..\Scripting\idgen.js $(InputName)_i.c $(InputName)_id.h $(InputName)_sp.h
Output files
- $(InputName).tlb
- $(InputName)_i.c
- $(InputName).h
- $(InputName)_id.h
- $(InputName)_sp.h
After that, you can even forget about MIDL-generated file_i.c that contains static GUIDs, because file_id.h defines them for you via __declspec(selectany)
.
Single Step IUnknown Implementation: CUnkImpl<>
If you ever wanted to quickly implement IUnknown
on your new object and don't give up non-default C++ constructor, here is a simple example of how it could be done with the help of CUnkImpl class:
Simple IUnknown-compliant ref-counted object, to be used with STL and avoid copying:
struct CHelper: CUnkImpl<CHelper>
{
CHelper(LPCOLESTR name): m_name(name) { }
CComBSTR m_name;
...
};
vector< CStlAware< IPtr<CHelper, &IID_IUnknown> > > m_vec;
m_vec.push_back(new CHelper(L"Hello"));
Single interface:
class CHelper:
public CUnkImpl<CHelper, IHelper>
{
public:
...
}
Dual interface:
class CHelper:
public CUnkImpl<CHelper, IDispatchImpl<IHelper, &IID_IHelper, &LIBID_Helper> >
{
public:
CHelper(const VARIANT& v);
COM_MAP_DUAL_ENTRY(IHelper)
...
}
Several interfaces:
class CHelper:
public CUnkImpl<CHelper>,
public IDispatchImpl<IHelper, &IID_IHelper, &LIBID_Helper>,
public ISupportErrorInfoImpl<&IID_IHelper>
{
public:
CHelper(const VARIANT& v);
BEGIN_COM_MAP_UNKIMPL(CHelper)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IHelper)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP_UNKIMPL()
...
}
System Resource Wrapper Template: CAuto<>
It's convenient to wrap an acquired system resource with a C++ class, much the same way COM interfaces are wrapped with Smart Pointers. Any resource like memory, KERNEL/USER/GDI object etc. may be efficiently wrapped with Auto<>
class and not worry further about its reclamation, because C++ takes care to release the resource when the scope of its use is over. This poses more simpler, linear code structuring.
The methods of resource reclamation differs, so we need an easy way to customize the Auto<>
class. Let's define 'abstract' templatized helper class for this, CAutoDtor
:
template<typename T>
struct CAutoDtor {
static void Destroy(T v);
};
Here is the CAuto<>
class itself. It derives from CAutoDtor
and inherits its Destroy
method. Custom CAutoDtor
may be specified to provide custom resource reclamation:
template< typename T, class Dtor = CAutoDtor<T>, T invalid = (T)NULL>
class CAuto: public Dtor {
void operator=(const CAuto&) throw();
CAuto(const CAuto&) throw();
public:
CAuto(Dtor dtor = Dtor()) throw(): Dtor(dtor), v(invalid) {}
CAuto(T val, Dtor dtor = Dtor()) throw(): Dtor(dtor), v(val) {}
bool operator!() const throw() { return v == invalid; }
operator bool() const throw() { return v != invalid; }
operator T() const { return v; }
T operator=(const T val) throw()
{ if ( v != invalid ) Destroy(v); v = val; return v; }
T* operator&() throw() { _ASSERTE(v == invalid); return &v; }
T operator->() const throw() { _ASSERTE(v != invalid); return v; }
T Detach() { T tmp = v; v = invalid; return tmp; }
~CAuto() throw() { if ( v != invalid ) Destroy(v); }
void Release() throw() { if ( v != invalid ) { Destroy(v); v = invalid; } }
public:
typedef T T;
T v;
};
There are two ways to use CAuto<>
:
- Specialize
CAutoDtor<T>::Destroy
for any unambiguous resource type T
(Win32 STRICT
is implied):
template<> inline void CAutoDtor<HANDLE>::Destroy(HANDLE v) {
if ( !CloseHandle(v) ) _ASSERTE(FALSE); }
Wrap Win32 file handle:
typedef CAuto<HANDLE, CAutoDtor<HANDLE>, INVALID_HANDLE_VALUE> CAutoFile;
CAutoFile file = CreateFile(...);
- For any ambiguous resource type, it's possible to provide a custom helper class, e.g.:
Wrap COM allocated memory:
struct CCoFreeDtor {
static void Destroy(LPVOID v) { CoTaskMemFree(v); }
};
CAuto<LPOLESTR, CCoFreeDtor> olestr;
StringFromCLSID(CLSID_Bar, &olestr);
Wrap WinSockets SOCKET
:
struct CSocketDtor {
static void Destroy(SOCKET v) { closesocket(v); }
};
typedef CAuto<SOCKET, CSocketDtor, INVALID_SOCKET> CAutoSocket;
Wrap OLE Automation TYPEATTR
:
struct CTADtor {
ITypeInfoPtr m_p;
template<class Ptr> CTLADtor(const Ptr& p): m_p(p) {}
void Destroy(TYPEATTR* v) { m_p->ReleaseTypeAttr(v); }
};
CAuto<TYPEATTR*, CTADtor> typeAttr(NULL, CTADtor(pTypeInfo));
typeInfo->GetTypeAttr(&typeAttr);
Then you may pass the variable of CAuto<>
type to wherever the original resource type could be passed. In the long run, it's possible to put a small library of custom wrappers to reuse them.
From the first glance, the use of a class like CAuto<>
may seem to be complex and unneeded. This is not so; it's especially handy when being combined with automatic errors/exceptions handling, and allows to cut the source code size and complexity in about a half. Just compare these two sample implementations.
Win32 Callback Thunking (C++ Closures): CAuxThunk<>
We cannot pass a C++ non-static member address to Win32 API requiring the callback address. Obviously, this is because in order to invoke the method on the object, two addresses are required. In some cases, this is solvable. For example, EnumWindows API takes a reference value lParam
that it passes later to EnumWindowsProc callback. We may use lParam
to pass the object address. Unfortunately, many other APIs (e.g., SetWindowsHookEx/CBTProc) don't support the reference data. Thunks could be used to turn an object+method pair into the closure that could be passed as single address to such an API. It's a very suitable and efficient way to provide proper apartment threading and reentrancy support, and avoids heavy MFC-style techniques relying on Global Variables, Thread Local Storage, Critical Sections and Instance Maps.
Two similar thunk classes are provided: CAuxThunk and CAuxStdThunk, for thiscall (default) and __stdcall methods accordingly.
Here is an example of how to set up Win32 CBT Hook without use of global variables:
class CHook:
public CAuxThunk<CHook>
{
public:
CHook(): m_hhook(NULL)
{
InitThunk((TMFP)CBTHook, this);
}
LRESULT CBTHook(int nCode, WPARAM wParam, LPARAM lParam);
BOOL Hook() {
m_hook = SetWindowsHookEx(WH_CBT, (HOOKPROC)GetThunk(),
NULL, GetCurrentThreadId());
return (BOOL)m_hook;
}
HHOOK m_hhook;
...
};
LRESULT CHook::CBTHook(int nCode, WPARAM wParam, LPARAM lParam)
{
if ( nCode == HCBT_CREATEWND ) {
UnhookWindowsHookEx(m_hook);
HWND hwnd = (HWND)wParam;
...
}
return CallNextHookEx(m_hook, nCode, wParam, lParam);
}
How to post message without a window
Here is another important example. In the current COM implementation, all calls are synchronized. That is, a thread making a call into another apartment shall wait until the call returns (the exception is IAdviseSink
, its proxy releases the caller). Often, especially in the case of event handling, there is a need to postpone the call processing to let the caller to complete its task. Because we have no control over the client message loop, the only way to achieve this is to create a window and post a message to it. When the next message loop iteration occurs, the message would get dispatched and handled (in apartment-threaded environment only, of course). The window could be quite a significant overhead, especially if we have a very simple object without any visuals. Fortunately, there is a solution: to use the familiar SetTimer API, TimerProc callback and CAuxThunk to get the same effect that PostMessage would produce. It's possible to set up a one-shot timer, with zero interval, then kill it by ID at the TimerProc callback - the exact effect of PostMessage, and no need to create a window (of course, the timer callback would be invoked on the same thread that called SetTimer
). Without a thunk, it's not so easy to achieve this effect because TimerProc
doesn't take a reference data.
struct TIMEOUT: CAuxThunk<TIMEOUT> {
CONTEXT m_contex;
UINT m_timerID;
TIMEOUT(CONTEXT& contex): m_contex(contex)
{
InitThunk((TMFP)TimerProc, this);
m_timerID = ::SetTimer(NULL, 0, 0,
(TIMERPROC)GetThunk());
}
void TimerProc(HWND, UINT, UINT idEvent, DWORD dwTime);
};
void TIMEOUT::TimerProc(HWND, UINT, UINT idEvent, DWORD dwTime)
{
AuxKillTimer(NULL, m_timerID);
...
delete this;
}
HRESULT CSimpleObj::Post(CONTEXT& contex) {
new TIMEOUT(context);
}
struct AUXMSG: MSG {
AUXMSG* pPrev;
};
inline AUXAPI_(void) AuxRepostTimerMsg(AUXMSG* pMsg) {
if ( !pMsg ) return;
AuxRepostTimerMsg(pMsg->pPrev);
PostMessage(pMsg->hwnd, pMsg->message, pMsg->wParam, pMsg->lParam);
}
inline AUXAPI_(void) AuxRemoveTimerMsg(HWND hwnd, UINT timerID, AUXMSG* pPrev) {
AUXMSG msg;
while ( PeekMessage(&msg, hwnd, WM_TIMER, WM_TIMER, PM_NOYIELD | PM_REMOVE) ) {
if ( msg.wParam == timerID ) continue;
msg.pPrev = pPrev;
AuxRemoveTimerMsg(hwnd, timerID, &msg);
return;
}
AuxRepostTimerMsg(pPrev);
}
inline AUXAPI_(void) AuxKillTimer(HWND hwnd, UINT timerID) {
KillTimer(hwnd, timerID);
AuxRemoveTimerMsg(hwnd, timerID, NULL);
}
WindowsX.h Message Crackers in ATL Msg Map
The use of Win32 Message Crackers is handy, because they track down message parameters for us. WindowsX.h defines proper cracker almost for each documented Win32 messages. Just look inside WindowsX.h for interesting messages and cut & paste the cracker into your code. Check out: Introduction to STRICT and Message Crackers. You can mix ATL MESSAGE_HANDLER and ATL/AUX MESSAGE_CRACKER entries at the same msg map:
class ATL_NO_VTABLE CWindowedObject:
public CWindowImpl<CWindowedObject>
{
BEGIN_MSG_MAP(CWindowedObject)
MESSAGE_CRACKER(WM_DESTROY, Cls_OnDestroy)
MESSAGE_CRACKER(WM_SIZE, Cls_OnSize)
MESSAGE_HANDLER(WM_CUSTOM, OnCustom)
...
END_MSG_MAP()
DECLARE_DEFWNDPROC()
void Cls_OnDestroy(HWND) {
PostQuitMessage();
}
void Cls_OnSize(HWND, UINT state, int cx, int cy) {
...
FORWARD_WM_SIZE(m_hWnd, state, cx, cy, DefWndProc)
}
LRESULT OnCustom(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
...
return 0L;
}
...
};
How to #include ATL/AUX
To use ATL/AUX, you should include AtlAux.h, the main header, within your project's StdAfx.h. Here is a typical ATL/AUX StdAfx.h file:
#pragma once
#define STRICT
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#define _ATL_APARTMENT_THREADED
#ifdef _UNICODE
#ifndef UNICODE
#define UNICODE
#endif
#endif
#include <InetSDK.h> // major COM stuff
#include <windowsx.h> // message crackers
#undef SubclassWindow
#include <ExDispid.h> // WebBrowser
#include <ExDisp.h>
#include <MsHtmdid.h> // Trident
#include <MsHTML.h>
#include <ax_id.h> // redefine all the above IID_* via __uuidof()
#include <AtlAux.h>
#include <ax_sp.h> // smart pointers
#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>
#include <atlwin.h>
#include <atlctl.h>
#include <AtlAux.h>
#undef _R
#include <vector>
#include <functional>
#define _R _RETURN
The library main header file, AtlAux.h, consists of two parts: Self-dependent and ATL-dependent. The above sample StdAfx.h includes it twice: before and after ATL itself. Note, there is no name collision (_R
) with VC++ STL. The workaround is shown above (I personally prefer the excellent SGI STL implementation: it's free, evolving, more debugged, C++ exception neutral and kept in sync with ANSI standard, with more features available).
The first pass (before ATL) is optional; all self-dependent stuff gets defined here. It may be used to provide smart pointer classes for all COM interfaces declared above in StdAfx.h. More details on this:
- At first, ax_id.h header redefines all standard IIDs in the form of
IID_<Name>
to be __uuidof(<Name>)
, to eliminate a GUID duplication [otherwise, linker would place two physical chunks, for IID_IUnknown
and __uuidof(IUnknown)
]. Here is how ax_id.h looks like:
#if defined(__IUnknown_FWD_DEFINED__) && !defined(IID_IUnknown)
#define IID_IUnknown __uuidof(IUnknown)
#endif
...
#if defined(__IDispatch_FWD_DEFINED__) && !defined(IID_IDispatch)
#define IID_IDispatch __uuidof(IDispatch)
#endif
So, the ax_id.h is absolutely self-dependent and optional. It may be skipped and at the same time may be included several times, as far as other standard MIDL-generated headers are added.
- Then, AtlAux.h is included to define the IPtr Smart Pointer class as well as
_COM_SMARTPTR_TYPEDEF
macro. We do that to be sure that standard VC's comdef.h file (that may be included by chance later as a result of #import
directive generated code) will use our own C++ exception neutral IPtr Smart Pointer class. If this is not what you need, you may include comdef.h instead of AtlAux.h to use _com_ptr_t, or simply skip both the current and the following steps at all.
- At last, ax_sp.h is included. It consists of declarations of smart pointer wrappers for each standard interface, and looks like:
#ifdef __IUnknown_FWD_DEFINED__
_COM_SMARTPTR_TYPEDEF(IUnknown, IID_IUnknown);
#endif
...
#ifdef __IDispatch_FWD_DEFINED__
_COM_SMARTPTR_TYPEDEF(IDispatch, IID_IDispatch);
#endif
The above defines IUnknownPtr
and IDispatchPtr
smart pointer types. The ax_sp.h file is similar to ax_id.h by the rules it obeys to, i.e., it may be included several or zero times. Note however, if standard comdef.h is referenced in the same project, you must include ax_id.h because of difference between ax_sp.h's and comdef.h's declarations:
_COM_SMARTPTR_TYPEDEF(IUnknown, IID_IUnknown);
_COM_SMARTPTR_TYPEDEF(IUnknown, __uuidof(IUnknown));
That's right, the purpose of ax_id.h is to define IID_IUnknown
as __uuidof(IUnknown)
, so both declarations would match.
Both ax_id.h and ax_sp.h are generated with IIDs/Smart Pointers Generator for standard Platform SDK interfaces. You may use this tool for your own interfaces, too. The most recently generated ax_id.h and ax_sp.h are provided: ax_id.zip [38.6 Kb] (Sep'98 Platform SDK).
The second pass defines the rest and depends on ATL to be included before. Of course, if only a single pass (after ATL) is done, the definitions of both parts are available.
Self-dependent Library Part
Self-dependent subset doesn't rely on ATL and may be used in any COM project. Two major sub-parts are Error Handling and Diagnostics Macros and the Smart Pointer class, IPtr.
Macros:
APIs:
Classes:
ATL-dependent Library Part
Macros:
APIs:
Classes:
ATL/AUX Change Log:
- 1.10.0015
Added:
CAuxGetClassImpl
added.
CPtr
fixed.
- 1.10.0014
Added:
CAuxSimpleArray
, CAuxSimpleMap
_R_WIN32_ERROR
_S_LAST_ERROR
_SALLOC
, _RALLOC
V_ISIFACE
CAutoPtr::Release
, CAutoArray::Release
Updated and fixed:
CSinkImpl
CAuxByRefVar
AuxYield
, AuxSleep
(WM_CLOSE
handling)
offsetof
- 1.10.0012
AuxKillTimer
added
AuxReportError
fixed
- 1.10.0011
CAuxByRefVar
class added
AuxFireOnChanged
API added
AuxYield
, AuxSleep
API added
AuxIsProxy
API added
V_ISEMPTY
, V_ISBSTR
, V_ISBOOL(v)
, V_ISDISPATCH
, V_ISI4
, V_ISUNKNOWN
macros added.
- 1.10.0010
CPtr<>::CreateObject
added
AuxFormatString
API added
- 1.10.0009
AuxLoadString
API added
AuxCloneObject
API added
AuxObjectQS
API added
CAuxHold
class and AuxHold
API added
IPersistPropertyBag_Save
fixed to support ATL3 data map
- 1.10.0007
CRefCntImpl
/CPtr
CStlAware
is revised for CComBSTR
; CStlAware
is renamed to CStlAdapt
(CStlAware
is #define
d as CStlAdapt
for compatibility).
- 1.10.0006
CAuto
- 3rd param updated to work with VC6 compiler.
IPersistPropertyBag_Load
updated to work with VT_BSTR
and VT_VARIANT
class members (ATL3 only).
_R_LAST_ERROR
added.
- 1.10.0005
- new header file added to distribution: AuxEH.h (exception handling)
aux_error_message
is revised
VarCmp
fix revised
- 1.10.0004
- Unicode portability fix (sugg. by Shimon Crown)
CAuto
changed (now derives from Dtor
)
- 1.10.0003
IPersistPropertyBagHelper
is reworked to be compatible with ATL3 (and its name changed to CAuxPersistPropertyBagImpl
).
- 1.10.0002
aux_report_failure
updated to preserve GetLastError
value
_R_FALSE
- 1.10.0001
AuxQI
is defined conditionally
VarCmp
fix revised
- 1.10.0000
- The code was split by two parts: self-dependent and ATL-dependent ones, updated for more general ATL3 compliance.
- AuxCrt.cpp - new module added, custom
_ATL_MIN_CRT
support for global C++ object construction/destruction.
CSinkImpl
- ATL3-like event sink class added, TBD.
CSinkBase
- changed
AuxFireEvent
- API added
CAuxObjectStack
- class added
CAuxEnum
- adopted for ATL3 (sugg. by Vladyslav Hrybok)
_R_OK
, _R_FAIL
(return), _BP
(HRESULT
breakpoint) - macros added
AuxQS
(QueryService) - API added
- Known ATL3 ['VarCmp' : function does not take 3 parameters] bug workaround
- Several names changed to be more consistent, sorry for inconveniences
- 1.02.0002
StrLen
, StrCmp
, StrLower
, StrUpper
, LowerCase
, UpperCase
- APIs added
AUXAPI_
, AUXAPI
, AUXAPIV_
, AUXAPIV
- macros added
- 1.02.0001
- minor doc clean-ups
COM_MAP_DUAL_ENTRY
, COM_MAP_DUAL_ENTRY_IID
added
- 1.02.0000
- Note: minor portability problems may arise
- ATL3 adoption to accommodate changes made to
END_COM_MAP
(ATL team has added IUnknown
ambiguity resolution there): DECLARE_UNKNOWN
(not required anymore), BEGIN_COM_MAP_UNKIMPL
/END_COM_MAP_UNKIMPL
of CUnkImpl
.
IPtr::CopyTo
, IPtr::IsEqualObject
CComLock
renamed to CAuxLock
, because of name collisions. I apologize for inconveniences.
- 1.01.0006
- New Win32 Callback thunking (cool)
_R
macros added
CStlAdapt
minor clean-ups
- 1.01.0005
- 1.01.0004
_iface_name
doesn't rely on CRegKey
anymore.
- 1.01.0003
- COMSUPP.LIB dispinterface helpers prototyped.
- 1.01.0002
CAuto-
, CAutoPtr::operator=
changed to be consistent with IPtr
, operator bool()
added.
- 1.01.0001
- 1.01.0000
CUnkImpl
, single-step IUnknown
implementation
CopyInterface
, CopyInterfaceQI
- error diagnostics significantly improved (
_error_message
) and extended: _HR
, _S
, _S_VAR
, _REPORT
, ASSERT_HR
, VERIFY_HR
.
GetLastResult()
, CAuxGuids::ReportErrorf
CModuleLock
_COM_SMARTPTR_TYPEDEF
is defined to use IPtr
by default
- 1.00.0057
DECLARE_DEFWNDPROC
for message crackers.
- 1.00.0056
ASSERT
, VERIFY
(to reduce the number of helper includes).
- 1.00.0054
CStlAdapt
fix (default constructor added)
CAuto
fix
IPtr
(int null
)
- 1.00.0053
CAuto::operator&
, CCoFreeDtor
.
- 1.00.0051
_FAILED
, _SUCCEEDED
error diagnostics added.
- 1.00.0050
- Error checking enhanced. Now you can click at error line in debug output window and you get to the corresponding point of the code!
- 1.00.0049
- Alternative QI:
_IUnknown
(TBD)
- 1.00.0047
GetLock
bug fixed. Assembling this header first time, I made some stupid changes over working code.
AuxQI
changed to be compatible with querying CCom[QI]Ptr
.
V_FALSE
/V_TRUE
added. Should have been done a long ago.