Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Small ATL Tricks: Part One

0.00/5 (No votes)
1 Oct 2002 5  
This series of articles demonstrates how to use some of the cool features of ATL in a time and energy saving manner.

Sample Image - SmallATLTricks1.jpg

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:

  1. 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()
    
      ...
      ...
    
    // 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.

  2. 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()
            '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 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 LPOLESTRs. 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.

  3. 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:

    // 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 for IErrorInfo 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 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)
    {
      // 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 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here