Click here to Skip to main content
15,393,377 members
Articles / Desktop Programming / ATL
Posted 17 Mar 2000

Tagged as


84 bookmarked

ATL/AUX Library

Rate me:
Please Sign up or sign in to vote.
4.11/5 (5 votes)
17 Mar 2000CPOL
A set of VC++ helpers and patterns to help automate some routine coding tasks.


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 

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 { // to auto-reclaim TLIBATTR memory
  ITypeLibPtr m_p;
  template<class Ptr> CTLADtor(const Ptr& p): m_p(p) {}
  void Destroy(TLIBATTR* v) { m_p->ReleaseTLibAttr(v); }
struct CTADtor { // to auto-reclaim TYPEATTR memory
  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;
  // First see if the object is willing to tell us about the
  // default source interface via IProvideClassInfo2
  IProvideClassInfo2Ptr spInfo2 = punkObj;
  if ( spInfo2 ) {
  // No, we have to go hunt for it
  ITypeInfoPtr spInfoCoClass;
  // If we have a clsid, use that
  /* This is missed in ATL3 AtlGetObjectSourceInterface -A. */
  IProvideClassInfoPtr spInfo = punkObj;
  if ( spInfo )
    _S( spInfo->GetClassInfo(&spInfoCoClass) )
    // Otherwise, try to locate the clsid from IPersist
    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) );
  for ( int i = 0; i < pAttr->cImplTypes; i++ )
    int nType;
    _S( spInfoCoClass->GetImplTypeFlags(i, &nType) );
      // we found it
      _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));

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)
  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;
            // First see if the object is willing to tell us about the
            // default source interface via IProvideClassInfo2
            CComPtr<IProvideClassInfo2> spInfo;
            hr = punkObj->QueryInterface(IID_IProvideClassInfo2, 
            if (SUCCEEDED(hr) && spInfo != NULL)
              hr = spInfo->GetGUID(GUIDKIND_DEFAULT_SOURCE_DISP_IID, piid);
              // No, we have to go hunt for it
              CComPtr<ITypeInfo> spInfoCoClass;
              // If we have a clsid, use that
              // Otherwise, try to locate the clsid from IPersist
              CComPtr<IPersist> spPersist;
              CLSID clsid;
              hr = punkObj->QueryInterface(IID_IPersist, 
              if (SUCCEEDED(hr))
                hr = spPersist->GetClassID(&clsid);
                if (SUCCEEDED(hr))
                  hr = spTypeLib->GetTypeInfoOfGuid(clsid, &spInfoCoClass);
                  if (SUCCEEDED(hr))
                    TYPEATTR* pAttr=NULL;
                    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 | 
                            // we found it
                            hr = 
                              spInfoCoClass->GetRefTypeOfImplType(i, &hRef);
                            if (SUCCEEDED(hr))
                              CComPtr<ITypeInfo> spInfo;
                              hr = 
                                spInfoCoClass->GetRefTypeInfo(hRef, &spInfo);
                              if (SUCCEEDED(hr))
                                TYPEATTR* pAttrIF;
                                if (pAttrIF != NULL)
                                  memcpy(piid, &pAttrIF->guid, sizeof(GUID));
  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); // return the HRESULT of p->Test1()
_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)
  // catch all exception possible with TestObject;
  // return E_FAIL if any occured
  _ON_ERROR(TestObject(obj1), return E_FAIL); 
  // this #import-generated wrapper may throw _com_error
  ITestPtr test(obj1);
  _SAFE( test->AddObject(this) );
  // put the rest in the try {} block
  try {
    // throw _com_error of E_POINTER
    if ( !obj2 ) _THROW( E_POINTER );
    // check HRESULT and throw _com_error upon an error
    _THROW( raw_TestObject(obj2) ); 
    // this #import-generated wrapper may throw _com_error
    ITestPtr test(obj2);
    // ...
    // this may throw AFX CFileException
    CFile(m_strFile, CFile::modeRead); 
    // ...
    return S_OK;
  _CATCH_ALL() // catch all exceptions (_com_error, CException*, ...) here

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);
  Base* p = NULL;
  ATLTRY(p = new Base())
  if (p != NULL)
    hRes = p->FinalConstruct();
    if (hRes != S_OK)
      delete p;
      p = NULL;
  *pp = p;
  return hRes;
  • Here is how to construct the object with AuxCreateObject:
    CComObject<CFoo>* p;
    _S( AuxCreateObject(&p) );
  • Or, for the case of CComObjectNoLock:
    CComObjectNoLock<CFoo>* p;
    _S( AuxCreateObject(&p) );

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,
  CParent* m_pParent;
  IDispatchPtr m_disp;

  void SetVoid(void* pv) {
    // pv could reference even data on caller stack, since
    // the call is immediately followed by FinalConstruct
    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;

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 {
  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:
// stdafx.cpp : source file that includes just the standard includes
//  stdafx.pch will be the pre-compiled header
//  stdafx.obj will contain the pre-compiled type information

#include <span class="code-string">"stdafx.h"</span>

#include <span class="code-keyword"><statreg.h></span>
#include <span class="code-keyword"><statreg.cpp></span>

#include <span class="code-keyword"><AuxCrt.cpp> // includes AtlImpl.cpp inside</span>

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> 
  // Lay out vtable with all event handlers in DWebBrowserEvents2 order.
  // Then you may override selected ones in the derived class.
  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) {}
  // alternatively, specify and lay out the v-table
  // only for selected events
  // (but comment out the above 'bulk' definitions)
    AUX_EVENT_NAME(OnVisible) // by name
    AUX_EVENT_NAME(StatusTextChange) // by name
  STDMETHOD_(void, OnQuit)() {}
  STDMETHOD_(void, OnVisible)(VARIANT_BOOL Visible) {}
  STDMETHOD_(void, StatusTextChange)(BSTR Text) {}

At the sink-derived class, we need to override selected methods to provide the actual processing:

class CSink: 
  public CComObjectRoot,
  public IUnknown,
  public DWebBrowserEvents2Sink
// ATL
  COM_MAP_NO_ENTRIES() // just IUnknown...

// IE events to handle...
  STDMETHOD_(void, StatusTextChange)(BSTR Text) {
    Log(_T("StatusTextChange: %ls\n"), Text);
  STDMETHOD_(void, OnQuit)() { 
  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: [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, \

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 [1.38 Kb] and its output for the latest Platform SDK interfaces: [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

ax_iidgen.gif (11808 bytes)

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>
  // IUnknown implementation is provided by CUnkImpl<CHelper>
  // non-default C++ constructor is allowed
  CHelper(LPCOLESTR name): m_name(name) { }

  CComBSTR m_name;
  // other stuff
// STL vector of smart pointers on CHelper objects
vector< CStlAware< IPtr<CHelper, &IID_IUnknown> > > m_vec;
// add new CHelper to vector
m_vec.push_back(new CHelper(L"Hello"));

Single interface:

class CHelper:
  public CUnkImpl<CHelper, IHelper>
// IHelper methods here

Dual interface:

class CHelper:
  public CUnkImpl<CHelper, IDispatchImpl<IHelper, &IID_IHelper, &LIBID_Helper> >
  CHelper(const VARIANT& v); // non-default ctor!

// IHelper methods here

Several interfaces:

class CHelper:
  public CUnkImpl<CHelper>,
  public IDispatchImpl<IHelper, &IID_IHelper, &LIBID_Helper>,
  public ISupportErrorInfoImpl<&IID_IHelper>
  CHelper(const VARIANT& v); // non-default ctor!


// IHelper methods here

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); // no implementation

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 {
  // disallow illegal constructions
  void operator=(const CAuto&) throw();
  CAuto(const CAuto&) throw();
  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; }
// #pragma warning(disable: 4284) 
// CAuto<>::operator ->' is not a UDT or reference to a UDT
  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; } }
  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));

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:
  // derive from thunk class
  public CAuxThunk<CHook>
  CHook(): m_hhook(NULL)
    // initialize thunk with class method address
    InitThunk((TMFP)CBTHook, this);

  LRESULT CBTHook(int nCode, WPARAM wParam, LPARAM lParam);
  BOOL Hook() {
    // pass the CBTHook closure to Win32 API
    m_hook = SetWindowsHookEx(WH_CBT, (HOOKPROC)GetThunk(), 
                                      NULL, GetCurrentThreadId());
    return (BOOL)m_hook;

  HHOOK m_hhook;
  // other stuff

LRESULT CHook::CBTHook(int nCode, WPARAM wParam, LPARAM lParam)
  // note, CHook::CBTHook is non-static class method, it has this pointer
  if ( nCode == HCBT_CREATEWND ) {
    HWND hwnd = (HWND)wParam;
    // do whatever we want with HWND
  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()); // zero interval
  void TimerProc(HWND, UINT, UINT idEvent, DWORD dwTime);

void TIMEOUT::TimerProc(HWND, UINT, UINT idEvent, DWORD dwTime)
  AuxKillTimer(NULL, m_timerID); // one-shot callback
  // do any processing task
  delete this;
// postpone the context processing until next message pump
HRESULT CSimpleObj::Post(CONTEXT& contex) {
  new TIMEOUT(context);
// AuxKillTimer - removes pending WM_TIMER messages

struct AUXMSG: MSG {
  AUXMSG* pPrev;

inline AUXAPI_(void) AuxRepostTimerMsg(AUXMSG* pMsg) {
  if ( !pMsg ) return;
  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);

inline AUXAPI_(void) AuxKillTimer(HWND hwnd, UINT timerID) {
  KillTimer(hwnd, timerID); // one-shot callback
  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>
  // message map
    MESSAGE_HANDLER(WM_CUSTOM, OnCustom) // no cracker, use hander
  DECLARE_DEFWNDPROC() // declare default DefWndProc for use with FORWARD_WM_*

  void Cls_OnDestroy(HWND) {
  void Cls_OnSize(HWND, UINT state, int cx, int cy) {
    // handle it
    // and forward for default processing
    FORWARD_WM_SIZE(m_hWnd, state, cx, cy, DefWndProc)
  LRESULT OnCustom(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) 
    // handle our own custom message
    return 0L;
  // other stuff

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

// Standard headers
#ifdef _UNICODE
#ifndef UNICODE
#define UNICODE // UNICODE is used by Windows headers

#include <span class="code-keyword"><InetSDK.h> // major COM stuff</span>
#include <span class="code-keyword"><windowsx.h> // message crackers</span>
#undef SubclassWindow // collision with atlwin.h

// Other COM headers (not included in InetSDK.h)
#include <span class="code-keyword"><ExDispid.h> // WebBrowser</span>
#include <span class="code-keyword"><ExDisp.h></span>
#include <span class="code-keyword"><MsHtmdid.h> // Trident</span>
#include <span class="code-keyword"><MsHTML.h></span>

// First ATL/AUX pass: self-dependent
#include <span class="code-keyword"><ax_id.h> // redefine all the above IID_* via __uuidof()</span>
#include <span class="code-keyword"><AtlAux.h> </span>
#include <span class="code-keyword"><ax_sp.h> // smart pointers</span>

// ATL 
#include <span class="code-keyword"><atlbase.h></span>
extern CComModule _Module;
#include <span class="code-keyword"><atlcom.h></span>
#include <span class="code-keyword"><atlwin.h></span>
#include <span class="code-keyword"><atlctl.h></span>

// second pass: ATL goodies
#include <span class="code-keyword"><AtlAux.h></span>

// VC++ STL
#undef _R // resolve the name collision with ATL/AUX
#include <span class="code-keyword"><vector></span>
#include <span class="code-keyword"><functional></span>
#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)
    #if defined(__IDispatch_FWD_DEFINED__) && !defined(IID_IDispatch)
    #define IID_IDispatch __uuidof(IDispatch)

    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);
    #ifdef __IDispatch_FWD_DEFINED__
    _COM_SMARTPTR_TYPEDEF(IDispatch, IID_IDispatch);

    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:

    // ax_sp.h
    _COM_SMARTPTR_TYPEDEF(IUnknown, IID_IUnknown);
    // comdef.h
    _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: [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.




ATL-dependent Library Part




ATL/AUX Change Log:

  • 1.10.0015


    • CAuxGetClassImpl added.
    • CPtr fixed.
  • 1.10.0014


    • CAuxSimpleArray, CAuxSimpleMap
    • _R_WIN32_ERROR
    • 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
  • 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 #defined 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
  • 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.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Andrew Nosenko
Australia Australia
No Biography provided

Comments and Discussions

GeneralThunk. Pin
HakunaMatada3-Apr-06 3:11
MemberHakunaMatada3-Apr-06 3:11 
GeneralRe: Thunk. Pin
Jerry Jeremiah1-Oct-08 16:03
MemberJerry Jeremiah1-Oct-08 16:03 
QuestionLink broken Pin
dvptUml20-Mar-06 15:33
MemberdvptUml20-Mar-06 15:33 
Hi Andrew,
I'm newer in programming and very interesting for your library version 1.10.0019, but all links are broken: as
And can't find it with search engine any more.

if anyone can tell me where to load it, thanks in advance.

Mat (dvptUml).
AnswerRe: Link broken Pin
John Patrick Francis8-Oct-07 11:23
MemberJohn Patrick Francis8-Oct-07 11:23 
GeneralCOM interface Pin
iforgot29-Jul-04 3:15
Memberiforgot29-Jul-04 3:15 
GeneralWow Pin
Jörgen Sigvardsson5-Dec-03 15:51
MemberJörgen Sigvardsson5-Dec-03 15:51 
GeneralImplementing Interface Pin
SuDG24-Feb-03 12:20
MemberSuDG24-Feb-03 12:20 
GeneralATL/AUX page has moved Pin
Andrew Nosenko2-Jan-02 3:03
MemberAndrew Nosenko2-Jan-02 3:03 
QuestionHow to post message without a window Pin
RainerS21-Aug-00 1:56
MemberRainerS21-Aug-00 1:56 
AnswerRe: How to post message without a window Pin
Andrew Nosenko22-Aug-00 0:05
MemberAndrew Nosenko22-Aug-00 0:05 
GeneralRe: How to post message without a window Pin
RainerS22-Aug-00 2:47
MemberRainerS22-Aug-00 2:47 
GeneralDWebBrowserEvents2 Pin
George16-Aug-00 5:18
MemberGeorge16-Aug-00 5:18 
GeneralRe: DWebBrowserEvents2 Pin
Andrew Nosenko22-Aug-00 0:07
MemberAndrew Nosenko22-Aug-00 0:07 
GeneralMost Recent version Pin
Arvind2331-Mar-00 15:18
MemberArvind2331-Mar-00 15:18 
GeneralRe: Most Recent version Pin
Andrew Nosenko31-Mar-00 21:54
MemberAndrew Nosenko31-Mar-00 21:54 
GeneralRe: Most Recent version Pin
1of318-Jun-04 20:24
Member1of318-Jun-04 20:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.