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