ATL错误分派机制详解

ATL编程中,错误分派是一个重要的概念,它允许COM组件向调用者提供详细的错误信息。本文将介绍如何在ATL中实现这一机制,包括使用ISupportErrorInfo、IErrorInfo和ICreateErrorInfo接口,并展示如何创建一个自定义的CError类来简化错误分派过程。

ATL中的错误分派机制

错误分派机制涉及到三个接口:ISupportErrorInfo、IErrorInfo和ICreateErrorInfo。以下是使COM组件支持错误信息的步骤:

如果使用ATL向导创建组件,请在创建Simple Object时勾选“Support ISupportErrorInfo”复选框。这一步会自动将ISupportErrorInfo添加到组件的基类列表中,并实现InterfaceSupportsErrorInfo函数。

以下是生成的代码示例:

Foo.h class ATL_NO_VTABLE CFoo : ... ... public ISupportErrorInfo, ... { ... ... BEGIN_COM_MAP(CFoo) ... COM_INTERFACE_ENTRY(ISupportErrorInfo) ... END_COM_MAP() ... ... // ISupportsErrorInfo STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid); }; Foo.cpp STDMETHODIMP CFoo::InterfaceSupportsErrorInfo(REFIID riid) { static const IID* arr[] = { &IID_IFoo }; for ( int i= 0 ; i < sizeof (arr) / sizeof (arr[0]); i++) { if (InlineIsEqualGUID(*arr[i],riid)) return S_OK; } return S_FALSE; }

这个函数的实现没有什么特别的,它只是告诉调用者对象支持错误信息,因此,如果组件的任何方法失败,调用者可以从组件查询错误信息。

第二步涉及编写一些自定义代码来分派错误。这段代码将在代码的许多地方使用,因此编写了一个小类来进行分派。

让介绍自己的CError类。这个类可以在源代码存档中的Error.h和Error.cpp文件中找到。这个类只是一个包含2个静态函数的集合,这些函数有助于分派错误。

第一个主要工作函数是DispatchError函数。函数原型如下:

HRESULT DispatchError(HRESULT hError, REFCLSID clsid, LPCTSTR szSource, LPCTSTR szDescription, DWORD dwHelpContext, LPCTSTR szHelpFileName);

该函数包含了ATL错误分派机制所需的大部分组件,以C++程序员友好的数据类型TCHAR字符串形式提供。该函数接受一个HRESULT作为错误标识符。对于想要向使用组件的程序员显示错误消息的大多数自定义错误,可以将其填充为E_FAIL。但是,如果错误是在程序运行时可能频繁发生并且需要显示给用户的错误,那么必须使用宏MAKE_HRESULT来创建它,以便于程序员识别。

使用此函数生成的错误将进入VB6中的Err对象,从中可以检索错误信息。以下是VB代码示例,用于捕获错误并显示适当的消息框:

VBScript Public Sub MySub() ' We must have a look at every error On Error GoTo ErrLabel .... .... ' If everything goes fine, avoid error handling ' by exiting from the subroutine Exit Sub ErrLabel: If Err.Number = MY_ERROR_CODE Then ' Code to display user friendly message Else ' We must throw back any unknown errors Err.Raise Err.Number, Err.Source, Err.Description, _ Err.HelpFile, Err.HelpContext End If End Sub

回到DispatchError(),该函数首先将每个字符串转换为LPOLESTR,这是错误分派接口所理解的。但在这样做之前,它会检查是否有任何字符串为NULL,如果是这样,它当然不会在LPOLESTR中设置相应的信息。如果提供的描述为NULL,函数会检查它是否可以自己获取一些错误信息。它检查错误是否为标准Win32错误代码:

if (HRESULT_FACILITY(hError) == FACILITY_WIN32) { // Code to get the Win32 error message }

在确认错误是标准Win32错误后,它使用FormatMessage() API调用来获取消息。

最后,准备好了ATL分派错误所需的一切。使用CreateErrorInfo API调用来创建一个ICreateErrorInfo对象,并用错误信息填充它,如下所示:

// Get the ICreateErrorInfo Interface ICreateErrorInfo *pCreateErrorInfo = NULL; HRESULT hSuccess = CreateErrorInfo(&pCreateErrorInfo); ATLASSERT(SUCCEEDED(hSuccess)); // Fill the error information into it pCreateErrorInfo->SetGUID(clsid); if (wszError != NULL) pCreateErrorInfo->SetDescription(wszError); if (wszSource != NULL) pCreateErrorInfo->SetSource(wszSource); if (wszHelpFile != NULL) pCreateErrorInfo->SetHelpFile(wszHelpFile); pCreateErrorInfo->SetHelpContext(dwHelpContext);

这一步之后,查询ICreateErrorInfo对象以获取IErrorInfo对象,该对象具有上面使用的所有Set函数的Get等效项。

// Get the IErrorInfo interface IErrorInfo *pErrorInfo = NULL; hSuccess = pCreateErrorInfo->QueryInterface(IID_IErrorInfo, (LPVOID *)&pErrorInfo);

然后,这个错误对象与当前线程关联,以便每当任何函数返回错误代码而不是S_OK时,调用者都可以从线程查询错误信息。使用以下API调用来将错误对象与当前线程关联:

// Set this error information in the current thread hSuccess = SetErrorInfo( 0 , pErrorInfo);

在准备好分派错误的类之后,从组件抛出错误就变成了一行任务:

Foo.cpp STDMETHODIMP CFoo::GenerateError(BSTR Message) { ... ... return CError::DispatchError(E_FAIL, // This represents the error CLSID_Foo, // This is the GUID of component throwing error _T( " Foo" ), // This is generally displayed as the title szMessage2, // This is the description 0 , // This is the context in the help file NULL); // This is the path to the help file }

现在需要介绍的另一个函数是DispatchWin32Error()。这个函数是DispatchError()的简单包装器,使组件设计者更容易抛出Win32错误。函数的实现如下所示:

HRESULT CError::DispatchWin32Error(DWORD dwError, REFCLSID clsid, LPCTSTR szSource, DWORD dwHelpContext, LPCTSTR szHelpFileName) { // Dispatch the requested error message return DispatchError( HRESULT_FROM_WIN32(dwError), // Convert error no. to HRESULT clsid, szSource, NULL, dwHelpContext, szHelpFileName); }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485