65.9K
CodeProject 正在变化。 阅读更多。
Home

ATL/AUX 库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.11/5 (5投票s)

2000年3月18日

CPOL
viewsIcon

193510

downloadIcon

1491

一组 VC++ 助手和模式,可帮助自动化一些日常编码任务。

引言

ATL/AUX 库是根据我使用 COM/ATL 的经验开发的一组 VC++ 助手和模式。该项目大约两年前为满足我自己的需求而启动。它帮助我自动化一些日常编码任务,并专注于问题本身。该库免费且正在不断发展,欢迎使用它,希望您能从中受益。要获取库更新和 ATL 开发技巧的通知,请订阅 ATL/AUX 公告栏。我将不胜感激任何技术反馈;无需订阅即可发帖。

新功能

CodeProject 上最新的 ATL/AUX 版本是 1.10.0015。要获取最新版本,请访问此处

ATL/AUX 库手册

版权所有 (c) Andrew Nosenko,1997-2000。

正在建设中

我仍然需要为每个宏/API/类提供详细描述,但我希望我已经涵盖了最有趣的 ATL/AUX 主题,并修订了旧内容并添加了新内容。请告诉我您的看法

概述主题

关于错误处理和诊断

宏集旨在促进 COM 代码调试过程,简化代码逻辑,同时提高其健壮性和可读性。

例如,在调试会话期间,我在 DevStudio 调试输出窗口中收到了以下错误输出

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'.]

然后我点击上面的行,自动跳转到导致错误的源代码行

  _S( m_view->StartModel(img, sound, 0) );

错误诊断的核心是 _HR(hr) 宏。它接受一个 HRESULT 值并跟踪所有可用的错误信息,如上述示例所示。格式化方式类似于编译器错误输出,Developer Studio 可以理解并打开受影响的源文件并将光标定位在发生错误的位置。跟踪错误信息后,_HR(hr) 简单地返回 hr,可以进一步分析。在 Release Build 中,_HR(hr) 简单地扩展为 *(hr)。_HR 宏可以在任何表达式或语句中使用,只要适当进行良好的调试诊断。

COM 编程中的常见情况是统一的 HRESULT 函数返回类型。我在 _HR 的基础上构建了 _S(hr) 宏,用于检查传入的 HRESULT 值并在指示错误时返回它。我更喜欢这种方法作为 C++ 异常处理的替代方案,因为它会生成更快更小的代码。为方便起见,还有 _R_R_OK_R_FAIL 返回

#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) 是另一个基于 _HR 的有用调试宏。如果 hr 指示错误,它会触发断点。

以下是几个(有点牵强)如何使用上述宏的草图

  • 简单方法
      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++ 构造函数
      CFoo::CFoo(HRESULT& hr) {
        _S_VAR(hr, m_bar.CreateInstance(CLSID_Bar));
        ...
        hr = S_OK;
      }
  • 没有 HRESULT 返回,但有很好的诊断
      if ( _FAILED(m_bar->Foo()) ) return;
  • 如果发生错误,则进入调试器。然后您可以遍历堆栈等。
      _BP( foo->Foo() );

以这种方式构造代码提供了简单的线性流控制。更重要的是,它拥有更安全、更健壮的代码,因为不再对某个调用成功做出假设。自动化错误(通过SetErrorInfo设置)会通过调用链冒泡并可供调用者使用。如果我们使用智能指针、自动指针和其他依赖于自动析构函数调用的堆栈帧上的 C++ 对象,我们可能无需费心释放获取的资源。查看CAuto 类,了解如何自动回收获取的资源。

其他错误处理相关宏:ASSERT_HRVERIFY_HR_FAILED_R_REPORT_S_VAR_SUCCEEDED

为了进行实验,我重写了 ATL3 AtlGetObjectSourceInterface API(来自 AtlCom.h)。该 API 检索传递对象上的默认源接口 GUID

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 ) {
    _S( spInfo2->GetGUID(GUIDKIND_DEFAULT_SOURCE_DISP_IID, piid) )
    _R_OK;
  }
  // 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) )
  else
  {
    // 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) );
  HREFTYPE hRef;
  for ( int i = 0; i < pAttr->cImplTypes; i++ )
  {
    int nType;
    _S( spInfoCoClass->GetImplTypeFlags(i, &nType) );
    if ( nType == (IMPLTYPEFLAG_FDEFAULT | IMPLTYPEFLAG_FSOURCE) )
    {
      // 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));
      _R_OK;
    }
  }
  _R_FAIL;
}

没有额外的分支,但每个调用都有全面的错误诊断。另请注意 CAuto 如何用于释放类型库结构。为了进行比较,以下是原始的 ATL3 代码(仅用于说明目的,版权所有 © 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);
            // 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, 
                                                  (void**)&spInfo);
            if (SUCCEEDED(hr) && spInfo != NULL)
              hr = spInfo->GetGUID(GUIDKIND_DEFAULT_SOURCE_DISP_IID, piid);
            else
            {
              // 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, 
                                       (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))
                          {
                            // we found it
                            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++ 异常处理

这是新的 AuxEH.h 头文件,用于在 COM 接口方法中处理 C++ 异常的宏。C++ 异常不得跨 COM 服务器方法边界抛出,因为客户端不期望处理它们。相反,应使用 SetErrorInfo 设置 IErrorInfo 并返回适当的 HRESULT

这个想法是使用单个语句轻松处理 _com_error、AFX CException 和所有其他可能的 (...) 异常,并具有与 _S_HR 宏类似的完整诊断。只需将可能抛出异常的代码放入 try { ... } 块中,并附加 _CATCH_COM()_CATCH_AFX()_CATCH_ANY(),它们的任何组合或 _CATCH_ALL()。丰富的错误信息 (IErrorInfo) 将被保留并传播到外部调用者。AFX 异常在 _CATCH_AFX() 块中转换为 IErrorInfo_CATCH_ALL() 定义为捕获所有可能的异常,并扩展为 _CATCH_COM() _CATCH_AFX() _CATCH_ANY()

您可以使用 _THROWHRESULT 失败时抛出 _com_error。它将查找已设置的 IErrorInfo 并获取其所有权。另一方面,_SAFE 可用于捕获由 #import 生成的高级包装器可能抛出的 _com_errorIErrorInfo 将被设置(如果有)并且 HRESULT 将返回给方法的调用者。_SAFE(exp)try { exp; } _CATCH_COM(); 的简写。

John F. Holliday 博士提出了另一个有用的宏:_ON_ERROR,它可以用一行代码统一处理 HRESULT 和异常抛出的错误。感谢 John!我对其进行了修改,添加了 AFX 支持和 $hr(错误代码)。正如 John 所指出的,_ON_ERROR 是查找仅以异常形式出现的细微错误的真正省时工具;缺点是它生成的代码量。示例

_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 头文件应在项目的 StdAfx.h 中包含在 AtlAux.h 之后。以下是在使用 MFC 的 ATL 服务器中使用上述宏的示例

STDMETHODIMP CTest::Init(IDispatch *obj1, IDispatch *obj2)
{
  AFX_MANAGE_STATE(AfxGetStaticModuleState());
  // 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);
    test->AddObject(this);
    // ...
    // this may throw AFX CFileException
    CFile(m_strFile, CFile::modeRead); 
    // ...
    return S_OK;
  }
  _CATCH_ALL() // catch all exceptions (_com_error, CException*, ...) here
}

供参考,这里有一篇关于编译器 COM 支持(_com_error#import 等)的精彩文章:Microsoft Visual C++ 5.0 编译器原生 COM 支持

ATL 对象的动态创建

手动创建 ATL 对象的常见方法如下

CComObject<CFoo>* p;
_S( CComObject<CFoo>::CreateInstance(&p) );

除了大量的输入,我们还仅限于 CComObject 包装器(对于 ATL3,CreateInstance 也可用于 CComAggObjectCComPolyObject)。我制作了一个简单的模板化 AuxCreateObject API,以 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;
}
  • 以下是如何使用 AuxCreateObject 构造对象
      CComObject<CFoo>* p;
      _S( AuxCreateObject(&p) );
  • 或者,对于 CComObjectNoLock 的情况
      CComObjectNoLock<CFoo>* p;
      _S( AuxCreateObject(&p) );

AuxCreateObject 的一个绝妙之处在于它接受一个可选的 pCtx 参数,并在构造过程中将其传递给对象。ATL 阻止我们使用非默认构造函数(因为有派生的 CComObject 族包装器)。传递初始化信息的唯一方法是提供我们自己的 SetVoid(void* pCtx) 方法,存储传递的 pCtx 指针,然后在 FinalConstruct 调用时使用它。这就是 AuxCreateObject 接受第二个参数的原因。以下是对象以这种方式初始化的示例

  class ATL_NO_VTABLE CHelper:
    public CComObjectRoot,
    public IHelper,
    ...
  {
  public:
    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) );
    }
    ...
  };

要构造这样一个 CHelper 对象,我们使用 AuxCreateObject。请注意,对 CHelper 对象未执行 AddRef,以与 CComObject::CreateInstance 采取相同的方式

HRESULT CParent::CreateHelper(CHelper** pHelper) {
  CComObject<CHelper>* p;
  _S( AuxCreateObject(&p, this) );
  *pHelper = p;
  _R_OK;
}

请注意,如果 FinalConstruct() 失败,对象将自动销毁,并且 AuxCreateObject 将返回失败代码。

最近的纯 ATL 解决方案是使用 ATL CComCreator<>::CreateInstance 方法。然而,这不尽相同;它返回请求的接口,而不是对象,并执行 AddRefCComCreator 由 ATL 类工厂使用。

HRESULT CParent::CreateHelper(IHelper** helper) {
  _R( CComCreator< CComObject< CHelper > >::CreateInstance(this, 
                                              IID_IHelper,(void**)helper) );
}

顺便说一下,请注意 FinalConstructSetVoid 都不是虚函数。ATL 几乎不使用虚函数。它使用模板和 C++ 继承规则来完成工作。看,基本的 FinalConstructSetVoid 在我们的对象派生自的 CComObjectRootBase 中定义。在我们的对象中,我们覆盖了这些方法(但它们不是虚函数!),因此 CComObject<> 派生自我们的对象并继承我们的实现。毕竟,CComCreator::CreateInstance 调用了 SetVoidFinalConstruct,而这些恰好是我们的实现。

另请注意,根据定义,依赖于通过 SetVoid 设置的初始化信息的 ATL 对象不能支持聚合。

当然,我们总是可以将初始化分为两部分,并提供单独的方法,例如 CHelper::Init(CParent* m_pParent),以将初始化作为单独的步骤进行。

最后一点,上述所有构造方法可能过于复杂。对于简单的对象,我们可以使用 CUnkImpl<> 类,它允许非默认的 C++ 构造函数(因为没有从我们的对象派生的对象)。

FYI,Don Box 在他 97 年 7 月的 MSJ 上的精彩专栏中讨论了 COM 对象构造的一些方面。

用于全局 C++ 对象构造/析构的自定义 _ATL_MIN_CRT:AuxCrt.cpp

标准 _ATL_MIN_CRT 支持在消除 CRT 开销方面做得很好。但不幸的是,它不支持使用全局 C++ 构造,例如以下内容

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;

上述操作将导致链接器冲突,因为会引用用于构造函数/析构函数调用的 CRT 代码。为了克服这个问题,我制作了 AuxCrt.cpp,它是 AtlImpl.cpp 的一个小型替代品,以相同的成本(即没有额外的开销)提供了所需的 CRT 功能。它的使用很简单

  • 不要在项目 Linker/Output Options 选项卡中指定 DllMain 作为入口点(或指定 _DllMainCRTStartup)。
  • StdAfx.cpp 文件中用 AuxCrt.cpp 替换 AtlImpl.cpp
// 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 "stdafx.h"

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif

#include <AuxCrt.cpp> // includes AtlImpl.cpp inside

Dispinterface 事件接收器实现:ISinkImpl<>

这个想法是使用布局合适的 C++ 虚函数来处理源 dispinterface 事件。我需要维护一些 ATL2 项目,并且长期以来一直在研究 ISinkImpl。ATL3 IDispEventImpl 帮助很大,但我的方法略有不同。我使用了一个中间接收器类,它定义了要由派生类覆盖的虚函数。我相信它更具可重用性,因为从长远来看,我有一系列我喜欢的接收器。例如,以下是 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_BEGIN_EVENT_MAP()
    AUX_EVENT_ID(DISPID_ONQUIT) // by DISPID
    AUX_EVENT_NAME(OnVisible) // by name
    AUX_EVENT_NAME(StatusTextChange) // by name
  AUX_END_EVENT_MAP()
  STDMETHOD_(void, OnQuit)() {}
  STDMETHOD_(void, OnVisible)(VARIANT_BOOL Visible) {}
  STDMETHOD_(void, StatusTextChange)(BSTR Text) {}
*/
};

在派生自接收器的类中,我们需要覆盖选定的方法以提供实际处理

class CSink: 
  public CComObjectRoot,
  public IUnknown,
  public DWebBrowserEvents2Sink
{
public:
// 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)() { 
    Log(_T("OnQuit\n"));
    //PostQuitMessage(0);
  }
  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));
  }
};

当然,说明 ISinkImpl<> 的最佳方法是举例。我提供了一个完整的客户端示例,即 IE4 自动化控制器。它创建了一个 Internet Explorer 应用程序实例,导航到 MS 网站,并记录它触发的所有事件。该示例可在此处获取:IEClient.zip [64.6 Kb]。该项目位于 ZIP 的 \IEClient 文件夹中;它应该可以在 VC5 和 VC6 上编译。所有其他与 ATL 相关的内容都在 \Shared 文件夹中。在那里,您可能会找到另一个可重用类 CLogWindow (AuxLog.h),用于在单独的窗口中记录 IE4 事件。

供参考,关于 标准 ATL3 IDispEventImpl 方法的良好说明可在此处找到:AtlEvnt.exe 使用 IDispEventImpl 创建 ATL 接收器

IIDs/智能指针生成器

我非常习惯 VC5+ __uuidof() 扩展(有没有人使用非 VC 编译器使用 COM/ATL?:))。但我不喜欢将 IID_<Name>__uuidof(<Name>) 样式混合使用。ATL 的构建方式是使用 IID_* 样式;我大量使用智能指针,这需要 __uuidof(*)。同时拥有 IID_IUnknown__uuidof(IUnknown) 将在最终二进制文件中放置两个相同的 GUID

另一个问题是命名便利性。我使用自己的 IPtr<>,尽管您可能使用标准 CCom[QI]Ptr (atlbase.h) 或 _com_ptr_t (comdef.h)。无论如何,每次输入 IPtr<IMyLovelyIface> 甚至更糟糕的 IPtr<IMyLovelyIface, &IID_IMyLovelyIface> 都很无聊。我更喜欢 IMyLovelyIfacePtr。VC5 comdef.h(编译器 COM 支持)文件允许定义自己的智能指针类,并为大多数 COM 接口提供 IUnknownPtr 等智能指针定义。此外,#import 指令可以为自定义类型库生成 IMyLovelyIfacePtr,例如

#define _COM_SMARTPTR IPtr

#import "MyLovelyLib.tlb" \
    raw_interfaces_only, \
    named_guids, \
    no_namespace

但对于其他大量的接口,包括那些随频繁更新的 INetSDK/Platform SDK 等而来的新接口呢?

我开发了 WSH (Windows Scripting Host) 脚本 IDGen.js,以解决上述两个问题。下载 ax_idgen.zip [1.38 Kb] 及其针对最新 Platform SDK 接口的输出:ax_id.zip [38.6 Kb]

这些文件是使用以下命令构建的

dumpbin /LINKERMEMBER Uuid.lib >ax cscript idgen.js ax

查看使用 ATL/AUX 的典型的 StdAfx.h 文件。这样,所有 IID_* 在 ATL 引用它们之前都被重新定义为 __uuidof(*)。请注意,您可以多次包含 ax_id.h,随着您添加新的 MIDL 生成的 SDK 头文件。

[注意:以下部分最初是为 VC5 编写的。我很快会将其更新为 VC6。] 除了标准接口,我还将生成器用于我的自己的 IDL 文件。为了自动化,我将其作为构建过程的一部分使用。要尝试此操作,请选择您的 IDL 的设置,然后在自定义构建中添加

构建命令

  • midl $(InputPath)
  • cscript ..\Scripting\idgen.js $(InputName)_i.c $(InputName)_id.h $(InputName)_sp.h

输出文件

  • $(InputName).tlb
  • $(InputName)_i.c
  • $(InputName).h
  • $(InputName)_id.h
  • $(InputName)_sp.h

ax_iidgen.gif (11808 bytes)

之后,你甚至可以忘记 MIDL 生成的包含静态 GUID 的 file_i.c,因为 file_id.h 通过 __declspec(selectany) 为你定义了它们。

单步 IUnknown 实现:CUnkImpl<>

如果您曾经想在您的新对象上快速实现 IUnknown 并且不想放弃非默认 C++ 构造函数,这里有一个简单的示例说明如何借助 CUnkImpl 类完成此操作

简单的符合 IUnknown 的引用计数对象,可与 STL 配合使用并避免复制

  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"));

单个接口

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

双接口

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

  // IHelper methods here
  ...
  }

多个接口

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

    BEGIN_COM_MAP_UNKIMPL(CHelper)
      COM_INTERFACE_ENTRY(IDispatch)
      COM_INTERFACE_ENTRY(IHelper)
      COM_INTERFACE_ENTRY(ISupportErrorInfo)
    END_COM_MAP_UNKIMPL()

  // IHelper methods here
  ...
  }

系统资源包装器模板:CAuto<>

用 C++ 类包装获取的系统资源很方便,就像用智能指针包装 COM 接口一样。任何资源,如内存、KERNEL/USER/GDI 对象等,都可以用 Auto<> 类高效包装,并且不再担心其回收,因为 C++ 会在资源使用范围结束时负责释放资源。这使得代码结构更简单、更线性。

资源回收的方法各不相同,因此我们需要一种简单的方法来定制 Auto<> 类。让我们为此定义一个“抽象”模板化助手类 CAutoDtor

template<typename T>
struct CAutoDtor {
  static void Destroy(T v); // no implementation
};

这是 CAuto<> 类本身。它派生自 CAutoDtor 并继承其 Destroy 方法。可以指定自定义 CAutoDtor 以提供自定义资源回收

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();
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; }
// #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; } }
public:
  typedef T T;
  T v;
};

使用 CAuto<> 有两种方式

  • 为任何明确的资源类型 T 特化 CAutoDtor<T>::Destroy(暗示 Win32 STRICT
    template<> inline void CAutoDtor<HANDLE>::Destroy(HANDLE v) {
      if ( !CloseHandle(v) ) _ASSERTE(FALSE); }

    包装 Win32 文件句柄

    typedef CAuto<HANDLE, CAutoDtor<HANDLE>, INVALID_HANDLE_VALUE> CAutoFile;
    CAutoFile file = CreateFile(...);
  • 对于任何模糊的资源类型,可以提供一个自定义的帮助类,例如

    包装 COM 分配的内存

    struct CCoFreeDtor {
      static void Destroy(LPVOID v) { CoTaskMemFree(v); }
    };
    // ...
    CAuto<LPOLESTR, CCoFreeDtor> olestr;
    StringFromCLSID(CLSID_Bar, &olestr);

    包装 WinSockets SOCKET

    struct CSocketDtor {
      static void Destroy(SOCKET v) { closesocket(v); }
    };
    typedef CAuto<SOCKET, CSocketDtor, INVALID_SOCKET> CAutoSocket;

    包装 OLE 自动化 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);

然后你可以将 CAuto<> 类型的变量传递给任何可以传递原始资源类型的地方。从长远来看,可以建立一个小型自定义包装器库以重复使用它们。

乍一看,使用像 CAuto<> 这样的类可能看起来复杂且不必要。并非如此;它在与自动错误/异常处理结合使用时特别方便,并且可以将源代码大小和复杂性减少约一半。只需比较这两个示例实现

Win32 回调 Thunking (C++ 闭包):CAuxThunk<>

我们不能将 C++ 非静态成员地址传递给需要回调地址的 Win32 API。显然,这是因为为了在对象上调用方法,需要两个地址。在某些情况下,这是可解决的。例如,EnumWindows API 接受一个引用值 lParam,它稍后会将其传递给 EnumWindowsProc 回调。我们可以使用 lParam 传递对象地址。不幸的是,许多其他 API(例如,SetWindowsHookEx/CBTProc)不支持引用数据。Thunk 可用于将对象+方法对转换为闭包,该闭包可以作为单个地址传递给此类 API。这是一种非常合适且有效的方式,可以提供适当的单元线程和重入支持,并避免依赖全局变量、线程本地存储、临界区和实例映射的繁重的 MFC 风格技术。

提供了两个类似的 thunk 类:CAuxThunkCAuxStdThunk,分别用于 thiscall(默认)和 __stdcall 方法。

这是一个在不使用全局变量的情况下设置 Win32 CBT Hook 的示例

class CHook:
  // derive from thunk class
  public CAuxThunk<CHook>
{
public:
  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 ) {
    UnhookWindowsHookEx(m_hook);
    HWND hwnd = (HWND)wParam;
    // do whatever we want with HWND
    ...
  }
  return CallNextHookEx(m_hook, nCode, wParam, lParam);
}

如何在没有窗口的情况下发送消息

这是另一个重要的例子。在当前的 COM 实现中,所有调用都是同步的。也就是说,一个线程向另一个单元发出调用时,必须等待该调用返回(IAdviseSink 是一个例外,其代理会释放调用者)。通常,尤其是在事件处理的情况下,需要推迟调用处理以允许调用者完成其任务。因为我们无法控制客户端消息循环,所以实现此目的的唯一方法是创建一个窗口并向其发送消息。当下一个消息循环迭代发生时,消息将被分派和处理(当然,仅在公寓线程环境中)。窗口可能会带来相当大的开销,特别是如果我们有一个非常简单的对象而没有任何视觉效果。幸运的是,有一个解决方案:使用熟悉的 SetTimer API、TimerProc 回调和 CAuxThunk 来获得与 PostMessage 相同效果。可以设置一个一次性计时器,间隔为零,然后在 TimerProc 回调中通过 ID 终止它——这与 PostMessage 的效果完全相同,并且无需创建窗口(当然,计时器回调将在调用 SetTimer 的同一线程上调用)。如果没有 thunk,实现这种效果并不容易,因为 TimerProc 不接受引用数据。

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;
  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); // one-shot callback
  AuxRemoveTimerMsg(hwnd, timerID, NULL);
}

ATL 消息映射中的 WindowsX.h 消息解析器

使用 Win32 消息解析器很方便,因为它们会为我们跟踪消息参数。WindowsX.h 几乎为每个文档化的 Win32 消息定义了适当的解析器。只需在 WindowsX.h 中查找有趣的消息,然后剪切并粘贴解析器到您的代码中。请查看:STRICT 和消息解析器简介。您可以在同一个消息映射中混合使用 ATL MESSAGE_HANDLER 和 ATL/AUX MESSAGE_CRACKER 条目

class ATL_NO_VTABLE CWindowedObject: 
  public CWindowImpl<CWindowedObject>
{
  // message map
  BEGIN_MSG_MAP(CWindowedObject)
    MESSAGE_CRACKER(WM_DESTROY, Cls_OnDestroy)
    MESSAGE_CRACKER(WM_SIZE, Cls_OnSize)
    MESSAGE_HANDLER(WM_CUSTOM, OnCustom) // no cracker, use hander
    ...
  END_MSG_MAP()
  DECLARE_DEFWNDPROC() // declare default DefWndProc for use with FORWARD_WM_*

  void Cls_OnDestroy(HWND) {
    PostQuitMessage();
  }
  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
  ...
};

如何 #include ATL/AUX

要使用 ATL/AUX,您应该在项目中的 StdAfx.h 中包含主头文件 AtlAux.h。以下是典型的 ATL/AUX StdAfx.h 文件

#pragma once

#define STRICT
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#define _ATL_APARTMENT_THREADED

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

#include <InetSDK.h> // major COM stuff
#include <windowsx.h> // message crackers
#undef SubclassWindow // collision with atlwin.h

// Other COM headers (not included in InetSDK.h)
#include <ExDispid.h> // WebBrowser
#include <ExDisp.h>
#include <MsHtmdid.h> // Trident
#include <MsHTML.h>

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

// ATL 
#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>
#include <atlwin.h>
#include <atlctl.h>

// second pass: ATL goodies
#include <AtlAux.h>

// VC++ STL
#undef _R // resolve the name collision with ATL/AUX
#include <vector>
#include <functional>
#define _R _RETURN

库主头文件 AtlAux.h 由两部分组成:自依赖依赖 ATL。上面的示例 StdAfx.h 两次包含它:在 ATL 本身之前和之后。请注意,与 VC++ STL 没有名称冲突 (_R)。解决方法如上所示(我个人更喜欢出色的 SGI STL 实现:它免费、不断发展、经过更多调试、C++ 异常中立并与 ANSI 标准保持同步,并提供更多功能)。

第一次传递(在 ATL 之前)是可选的;所有自依赖内容都在此处定义。它可用于为 StdAfx.h 中声明的所有 COM 接口提供智能指针类。更多详细信息

  • 首先,ax_id.h 头文件将所有标准 IID 以 IID_<Name> 的形式重新定义为 __uuidof(<Name>),以消除 GUID 重复 [否则,链接器将为 IID_IUnknown__uuidof(IUnknown) 放置两个物理块]。以下是 ax_id.h 的样子
    #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

    因此,ax_id.h 绝对是自依赖且可选的。它可以跳过,同时可以多次包含,只要添加了其他标准 MIDL 生成的头文件。

  • 然后,包含 AtlAux.h 以定义 IPtr 智能指针类以及 _COM_SMARTPTR_TYPEDEF 宏。我们这样做是为了确保标准的 VC comdef.h 文件(可能由于 #import 指令生成的代码而稍后意外包含)将使用我们自己的 C++ 异常中立 IPtr 智能指针类。如果这不是您需要的,您可以包含 comdef.h 而不是 AtlAux.h 来使用 _com_ptr_t,或者完全跳过当前和以下步骤。
  • 最后,包含 ax_sp.h。它包含每个标准接口的智能指针包装器声明,看起来像
    #ifdef __IUnknown_FWD_DEFINED__
    _COM_SMARTPTR_TYPEDEF(IUnknown, IID_IUnknown);
    #endif
    ...
    #ifdef __IDispatch_FWD_DEFINED__
    _COM_SMARTPTR_TYPEDEF(IDispatch, IID_IDispatch);
    #endif

    上述定义了 IUnknownPtrIDispatchPtr 智能指针类型。ax_sp.h 文件在遵循的规则上与 ax_id.h 相似,即,它可以包含多次或零次。但是,请注意,如果在同一个项目中引用了标准 comdef.h,则由于 ax_sp.hcomdef.h 声明之间的差异,您必须包含 ax_id.h

    // ax_sp.h
    _COM_SMARTPTR_TYPEDEF(IUnknown, IID_IUnknown);
    // comdef.h
    _COM_SMARTPTR_TYPEDEF(IUnknown, __uuidof(IUnknown));

    没错,ax_id.h 的目的是将 IID_IUnknown 定义为 __uuidof(IUnknown),这样两个声明就会匹配。

    ax_id.hax_sp.h 都是使用IIDs/智能指针生成器为标准 Platform SDK 接口生成的。您也可以将此工具用于您自己的接口。提供了最新生成的 ax_id.hax_sp.hax_id.zip [38.6 Kb](98 年 9 月 Platform SDK)。

第二次传递定义了其余部分,并依赖于 ATL 在之前被包含。当然,如果只进行一次传递(在 ATL 之后),则两部分的定义都可用。

自依赖库部分

自依赖子集不依赖 ATL,可用于任何 COM 项目。两个主要子部分是错误处理和诊断宏和智能指针类IPtr

API

依赖 ATL 的库部分

API

ATL/AUX 变更日志

  • 1.10.0015

    Added

    • CAuxGetClassImpl 添加。
    • CPtr 已修复。
  • 1.10.0014

    Added

    • CAuxSimpleArray, CAuxSimpleMap
    • _R_WIN32_ERROR
    • _S_LAST_ERROR
    • _SALLOC, _RALLOC
    • V_ISIFACE
    • CAutoPtr::Release, CAutoArray::Release

    更新并修复

    • CSinkImpl
    • CAuxByRefVar
    • AuxYield, AuxSleep (WM_CLOSE 处理)
    • offsetof
  • 1.10.0012
    • AuxKillTimer 已添加
    • AuxReportError 已修复
  • 1.10.0011
    • CAuxByRefVar 类已添加
    • AuxFireOnChanged API 已添加
    • AuxYield, AuxSleep API 已添加
    • AuxIsProxy API 已添加
    • V_ISEMPTY, V_ISBSTR, V_ISBOOL(v), V_ISDISPATCH, V_ISI4, V_ISUNKNOWN 宏已添加。
  • 1.10.0010
    • CPtr<>::CreateObject 已添加
    • AuxFormatString API 已添加
  • 1.10.0009
    • AuxLoadString API 已添加
    • AuxCloneObject API 已添加
    • AuxObjectQS API 已添加
    • CAuxHold 类和 AuxHold API 已添加
    • IPersistPropertyBag_Save 已修复以支持 ATL3 数据映射
  • 1.10.0007
    • CRefCntImpl/CPtr
    • CStlAware 已针对 CComBSTR 进行修订;CStlAware 已重命名为 CStlAdapt(为兼容性,CStlAware#defineCStlAdapt)。
  • 1.10.0006
    • CAuto - 第 3 个参数已更新,以兼容 VC6 编译器。
    • IPersistPropertyBag_Load 已更新以兼容 VT_BSTRVT_VARIANT 类成员(仅限 ATL3)。
    • _R_LAST_ERROR 已添加。
  • 1.10.0005
    • 新增头文件:AuxEH.h (异常处理)
    • aux_error_message 已修订
    • VarCmp 修复已修订
  • 1.10.0004
    • Unicode 可移植性修复(由 Shimon Crown 建议)
    • CAuto 已更改(现在从 Dtor 派生)
  • 1.10.0003
    • IPersistPropertyBagHelper 已重写以兼容 ATL3(其名称已更改为 CAuxPersistPropertyBagImpl)。
  • 1.10.0002
    • aux_report_failure 已更新以保留 GetLastError
    • _R_FALSE
  • 1.10.0001
    • AuxQI 条件定义
    • VarCmp 修复已修订
  • 1.10.0000
    • 代码分为两部分:自依赖和依赖 ATL,更新以更普遍地符合 ATL3。
    • AuxCrt.cpp - 新模块已添加,自定义 _ATL_MIN_CRT 支持全局 C++ 对象的构造/析构。
    • CSinkImpl - 类似 ATL3 的事件接收器类已添加,待定。
    • CSinkBase - 已更改
    • AuxFireEvent - API 已添加
    • CAuxObjectStack - 类已添加
    • CAuxEnum - 已适配 ATL3(由 Vladyslav Hrybok 建议)
    • _R_OK, _R_FAIL (返回), _BP (HRESULT 断点) - 宏已添加
    • AuxQS (QueryService) - API 已添加
    • 已知 ATL3 ['VarCmp':函数不接受 3 个参数] 错误解决方法
    • 一些名称已更改以更具一致性,抱歉带来不便
  • 1.02.0002
    • StrLen, StrCmp, StrLower, StrUpper, LowerCase, UpperCase - API 已添加
    • AUXAPI_, AUXAPI, AUXAPIV_, AUXAPIV - 宏已添加
  • 1.02.0001
    • 次要文档清理
    • COM_MAP_DUAL_ENTRY, COM_MAP_DUAL_ENTRY_IID 已添加
  • 1.02.0000
    • 注意:可能会出现次要的可移植性问题
    • ATL3 适配以适应 END_COM_MAP 的更改(ATL 团队已在那里添加了 IUnknown 歧义解析):DECLARE_UNKNOWN(不再需要),CUnkImplBEGIN_COM_MAP_UNKIMPL/END_COM_MAP_UNKIMPL
    • IPtr::CopyTo, IPtr::IsEqualObject
    • CComLock 已重命名为 CAuxLock,因为名称冲突。我为造成的不便表示歉意。
  • 1.01.0006
    • 新的 Win32 回调 thunking (很棒)
    • _R 宏已添加
    • CStlAdapt 次要清理
  • 1.01.0005
    • 次要 Unicode 可移植性修复(由 Darryl Yust 建议)。
  • 1.01.0004
    • _iface_name 不再依赖 CRegKey
  • 1.01.0003
    • COMSUPP.LIB dispinterface 助手已原型化。
  • 1.01.0002
    • CAuto-, CAutoPtr::operator= 已更改以与 IPtr 保持一致,operator bool() 已添加。
  • 1.01.0001
  • 1.01.0000
    • CUnkImpl,单步 IUnknown 实现
    • CopyInterface, CopyInterfaceQI
    • 错误诊断显著改进(_error_message)并扩展:_HR, _S, _S_VAR, _REPORT, ASSERT_HR, VERIFY_HR
    • GetLastResult(), CAuxGuids::ReportErrorf
    • CModuleLock
    • _COM_SMARTPTR_TYPEDEF 默认定义为使用 IPtr
  • 1.00.0057
    • 用于消息解析器的 DECLARE_DEFWNDPROC
  • 1.00.0056
    • ASSERT, VERIFY (减少辅助包含的数量)。
  • 1.00.0054
    • CStlAdapt 修复(添加了默认构造函数)
    • CAuto 修复
    • IPtr (int null)
  • 1.00.0053
    • CAuto::operator&, CCoFreeDtor
  • 1.00.0051
    • _FAILED, _SUCCEEDED 错误诊断已添加。
  • 1.00.0050
    • 错误检查功能增强。现在,您可以点击调试输出窗口中的错误行,然后跳转到代码中的相应位置!
  • 1.00.0049
    • 备用 QI: _IUnknown (待定)
  • 1.00.0047
    • GetLock 错误已修复。第一次组装此头文件时,我对工作代码做了一些愚蠢的更改。
    • AuxQI 已更改以与查询 CCom[QI]Ptr 兼容。
    • V_FALSE/V_TRUE 已添加。早就该做了。
© . All rights reserved.