Small ATL Tricks: Part One






4.74/5 (14 votes)
Oct 1, 2002
5 min read

120369

1252
This series of articles demonstrates how to use some of the cool features of ATL in a time and energy saving manner.
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 functionInterfaceSupportsErrorInfo
. 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() ... ... // 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; }
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 theDispatchError()
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 anHRESULT
as the error identifier. This can be filled in asE_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 anHRESULT
using the macroMAKE_HRESULT
to make it's identification easy for the programmer working on our component. The errors generated using this function gets into theErr
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() '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
Coming back to
DispatchError()
, the first thing that this function does is that it converts each string intoLPOLESTR
, 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 inLPOLESTR
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) { // Code to get the Win32 error message }
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 ofICreateErrorInfo
and fill it with the error information, as shown below:// 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);
After this step we query the
ICreateErrorInfo
object forIErrorInfo
object, which has got the Get equivalents of all the Set functions used above.// Get the IErrorInfo interface 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:
// Set this error information in the current thread 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, // 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 }
The other function that now remains to be introduced is
DispatchWin32Error()
. This function is a simple wrapper aroundDispatchError()
, 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) { // Dispatch the requested error message return DispatchError( HRESULT_FROM_WIN32(dwError), // Convert error no. to HRESULT clsid, szSource, NULL, dwHelpContext, szHelpFileName); }
The function converts the error number into
HRESULT
containing the Win32 facility code using the macroHRESULT_FROM_WIN32
. It also fillsNULL
in theszDescription
parameter, because it is aware of the fact that the functionDispatchError()
looks out for the message corresponding to Win32 error code embedded in theHRESULT
.
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.