ATL/AUX 库






4.11/5 (5投票s)
一组 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 主题,并修订了旧内容并添加了新内容。请告诉我您的看法。
概述主题
- 关于错误处理和诊断
- C++ 异常处理
- 用于全局 C++ 对象构造/析构的自定义 _ATL_MIN_CRT:AuxCrt.cpp
- Dispinterface 事件接收器实现:ISinkImpl<>
- ATL 对象的动态创建
- IID/智能指针生成器
- 单步 IUnknown 实现:CUnkImpl<>
- 系统资源包装器模板:CAuto<>
- Win32 回调 Thunking (C++ 闭包):CAuxThunk<>
- ATL 消息映射中的 WindowsX.h 消息解析器
关于错误处理和诊断
宏集旨在促进 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_HR、VERIFY_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()
。
您可以使用 _THROW
在 HRESULT
失败时抛出 _com_error
。它将查找已设置的 IErrorInfo
并获取其所有权。另一方面,_SAFE
可用于捕获由 #import
生成的高级包装器可能抛出的 _com_error
;IErrorInfo
将被设置(如果有)并且 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
也可用于 CComAggObject 和 CComPolyObject)。我制作了一个简单的模板化 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
方法。然而,这不尽相同;它返回请求的接口,而不是对象,并执行 AddRef
。CComCreator
由 ATL 类工厂使用。
HRESULT CParent::CreateHelper(IHelper** helper) { _R( CComCreator< CComObject< CHelper > >::CreateInstance(this, IID_IHelper,(void**)helper) ); }
顺便说一下,请注意 FinalConstruct
和 SetVoid
都不是虚函数。ATL 几乎不使用虚函数。它使用模板和 C++ 继承规则来完成工作。看,基本的 FinalConstruct
和 SetVoid
在我们的对象派生自的 CComObjectRootBase
中定义。在我们的对象中,我们覆盖了这些方法(但它们不是虚函数!),因此 CComObject<>
派生自我们的对象并继承我们的实现。毕竟,CComCreator::CreateInstance
调用了 SetVoid
和 FinalConstruct
,而这些恰好是我们的实现。
另请注意,根据定义,依赖于通过 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
之后,你甚至可以忘记 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
(暗示 Win32STRICT
)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 类:CAuxThunk 和 CAuxStdThunk,分别用于 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
上述定义了
IUnknownPtr
和IDispatchPtr
智能指针类型。ax_sp.h 文件在遵循的规则上与 ax_id.h 相似,即,它可以包含多次或零次。但是,请注意,如果在同一个项目中引用了标准 comdef.h,则由于 ax_sp.h 和 comdef.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.h 和 ax_sp.h 都是使用IIDs/智能指针生成器为标准 Platform SDK 接口生成的。您也可以将此工具用于您自己的接口。提供了最新生成的 ax_id.h 和 ax_sp.h:ax_id.zip [38.6 Kb](98 年 9 月 Platform SDK)。
第二次传递定义了其余部分,并依赖于 ATL 在之前被包含。当然,如果只进行一次传递(在 ATL 之后),则两部分的定义都可用。
自依赖库部分
自依赖子集不依赖 ATL,可用于任何 COM 项目。两个主要子部分是错误处理和诊断宏和智能指针类IPtr。
宏
API
- AuxGetLastResult
- AuxClearErrorInfo
- AuxCreateInstance
- AuxCopyInterface
- AuxCopyQI
- AuxGetLock
- AuxInternalQI
- AuxQI
- AuxQS
类
- CAuto, CAutoDtor, CCoFreeDtor
- CAutoArray
- CAutoPtr
- CAuxLock, AUXLOCK
- CAuxThunk, CAuxStdThunk
- IPtr
- CStlAware
依赖 ATL 的库部分
宏
- ASSERT
- ASSERT_HR
- BEGIN_COM_MAP_UNKIMPL
- END_COM_MAP_UNKIMPL
- _countof
- COM_MAP_NO_ENTRIES
- COM_MAP_SINGLE_ENTRY
- COM_MAP_SINGLE_ENTRY_IID
- COM_MAP_DUAL_ENTRY
- COM_MAP_DUAL_ENTRY_IID
- DECLARE_DEFWNDPROC
- DEBUG_ONLY
- MESSAGE_CRACKER
- _offsetof (
offsetof
) - V_FALSE, V_TRUE
- TRACE
- VERIFY
- VERIFY_HR
- _zeroinit (
ZERO_INIT
)
API
类
- CAuxGuids
- CAuxObjectStack
- CAuxObjectSafetyImpl
- CAuxPersistPropertyBag
- CModuleLock
- CSinkBase
- CUnkImpl
- CComEnumImplEx
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
被#define
为CStlAdapt
)。
- 1.10.0006
CAuto
- 第 3 个参数已更新,以兼容 VC6 编译器。IPersistPropertyBag_Load
已更新以兼容VT_BSTR
和VT_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
(不再需要),CUnkImpl
的BEGIN_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
_S_VAR
已修复(由 Jared Bluestein 建议)。
- 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
(待定)
- 备用 QI:
- 1.00.0047
GetLock
错误已修复。第一次组装此头文件时,我对工作代码做了一些愚蠢的更改。AuxQI
已更改以与查询CCom[QI]Ptr
兼容。V_FALSE
/V_TRUE
已添加。早就该做了。