Introduction
This article is the first in the series of articles demonstrating some of the ATL's wonderful features in the form of small techniques used for implementation. This article covers the mechanism of error dispatch. Many programmers (atleast myself) run away from this feature because of the complications involved in implementing this mechanism of throwing every small error occuring here and there. The methods provided in this article enable a developer to implement a strong design strategy in an extensible and usable fashion. This article primarily aims at writing the code in ATL 3.0 and using the code in VB6 or .NET.
Error Dispatch mechanism in ATL
This feature involves the use of three interfaces ISupportErrorInfo
, IErrorInfo
and ICreateErrorInfo
. The following are steps of making your COM component error information enabled:
- If you are using the ATL wizard for creation of your component, then you need to tick on the check box with the text Support ISupportErrorInfo while creating your Simple Object. This is as shown in the picture below:
This step automatically adds the name of ISupportErrorInfo
in the base class list of your component and implements the function InterfaceSupportsErrorInfo
. Shown below are the lines generated as a result, and the names of the files in the source project in which they can be found:
Foo.h
class ATL_NO_VTABLE CFoo :
...
...
public ISupportErrorInfo,
...
{
...
...
BEGIN_COM_MAP(CFoo)
...
COM_INTERFACE_ENTRY(ISupportErrorInfo)
...
END_COM_MAP()
...
...
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;
}
The implementation of this function has got nothing special. It just tells the caller that the object supports the error information and hence, if any of the methods of this component fails, then caller can query the error information from the component. The querying mechanism in C++ will take up another article itself and is thus not covered here. Moreover since the code is aimed to be used in VB6 and .NET, the error handling is easy enough because the error is raised as a run time exception automatically in the programs written in these environments.
- The second step involves writing a little of your own code for dispatching the error. This code will be used from a lot of places in the code, hence a small class is written for the dispatching purpose.
May I have the pleasure of introducing our very own home made CError
class. This class can be found in Error.h and Error.cpp files in the source archive. This class is nothing but a collection of 2 static functions that help in dispatching the errors. The first and main workhorse function is the DispatchError()
function. The prototype of the function is as follows:
HRESULT DispatchError(HRESULT hError, REFCLSID clsid,
LPCTSTR szSource, LPCTSTR szDescription,
DWORD dwHelpContext, LPCTSTR szHelpFileName);
The function has most of the components required by the ATL error dispatch mechanism in C++ programmer friendly datatypes, i.e TCHAR
strings. The function accepts an HRESULT
as the error identifier. This can be filled in as E_FAIL
for most of our custom errors for which we just want to display an error message to the programmer using our component. But, if by any chance the error is the one that might occur frequently during the runtime of the program and has to be shown to the user, then we must create an HRESULT
using the macro MAKE_HRESULT
to make it's identification easy for the programmer working on our component. The errors generated using this function gets into the Err
object under VB6, from which the error information can be retreived from it's properties. A typical VB code for trapping errors and displaying proper message boxes is as shown:
Public Sub MySub()
On Error GoTo ErrLabel
....
....
Exit Sub
ErrLabel:
If Err.Number = MY_ERROR_CODE Then
Else
Err.Raise Err.Number, Err.Source, Err.Description, _
Err.HelpFile, Err.HelpContext
End If
End Sub
Coming back to DispatchError()
, the first thing that this function does is that it converts each string into LPOLESTR
, which is understood by the error dispatch interfaces. But before doing so, it checks if any of the strings are NULL, which ofcourse if is the case it does not set the corresponding information in LPOLESTR
s. If the description provided is NULL, the function checks if it can obtain some error information by itself. It checks if the error is a standard Win32 error code in the following line:
if(HRESULT_FACILITY(hError) == FACILITY_WIN32)
{
}
After confirming that the error is a standard Win32 error, it gets the message using the FormatMessage()
API call.
- Finally we are ready with everything that ATL needs to dispatch an error. We use the
CreateErrorInfo
API call to create an object of ICreateErrorInfo
and fill it with the error information, as shown below:
ICreateErrorInfo *pCreateErrorInfo = NULL;
HRESULT hSuccess = CreateErrorInfo(&pCreateErrorInfo);
ATLASSERT(SUCCEEDED(hSuccess));
pCreateErrorInfo->SetGUID(clsid);
if(wszError != NULL)
pCreateErrorInfo->SetDescription(wszError);
if(wszSource != NULL)
pCreateErrorInfo->SetSource(wszSource);
if(wszHelpFile != NULL)
pCreateErrorInfo->SetHelpFile(wszHelpFile);
pCreateErrorInfo->SetHelpContext(dwHelpContext);
After this step we query the ICreateErrorInfo
object for IErrorInfo
object, which has got the Get equivalents of all the Set functions used above.
IErrorInfo *pErrorInfo = NULL;
hSuccess = pCreateErrorInfo->QueryInterface(IID_IErrorInfo,
(LPVOID *)&pErrorInfo);
This error object is then associated with the current thread, so that whenever any function returns an error code instead of S_OK, the caller can query the error information from the thread. The error object is associated with the current thread using the following API call:
hSuccess = SetErrorInfo(0, pErrorInfo);
After the class for dispatching errors is ready, throwing the error from a component is a one line task:
Foo.cpp
STDMETHODIMP CFoo::GenerateError(BSTR Message)
{
...
...
return CError::DispatchError(E_FAIL,
CLSID_Foo,
_T("Foo"),
szMessage2,
0,
NULL);
}
The other function that now remains to be introduced is DispatchWin32Error()
. This function is a simple wrapper around DispatchError()
, for making it easier for the component designer to throw Win32 errors. The implementation of the function is as shown below:
HRESULT CError::DispatchWin32Error(DWORD dwError, REFCLSID clsid,
LPCTSTR szSource, DWORD dwHelpContext, LPCTSTR szHelpFileName)
{
return DispatchError(
HRESULT_FROM_WIN32(dwError),
clsid, szSource, NULL, dwHelpContext,
szHelpFileName);
}
The function converts the error number into HRESULT
containing the Win32 facility code using the macro HRESULT_FROM_WIN32
. It also fills NULL
in the szDescription
parameter, because it is aware of the fact that the function DispatchError()
looks out for the message corresponding to Win32 error code embedded in the HRESULT
.
Conclusion
This concludes the error dispatch mechanism for ATL. We will move on to the next small technique of creating noncreatable objects in ATL in the next article.