Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Managing COM Errors with Macros


(Part 3 of 3) In the last column, it was obvious that writing code to detect and process rich error information provided by a COM interface is a lot of work (in C++). No wonder most developers don’t bother with it. The majority of COM code I’ve seen just tests for failed HRESULT values (using a common macro like #define FAILED) and then shunts off into some run-of-the-mill error code. Usually this is just dumping the error to a log or aborting the action at that point.

C++ is an awesome and powerful language; however it is also often a pain to work with, particularly if you are concentrating more on the logic of your application than the mechanics of the code. Reading through pages of code is not a whole lot of fun if it’s clouded with error detection and handling logic. Trying to determine the flow of the code is more work, and even for the original writer of the code, it probably explains why most developers pass on robust error handling.

This is unfortunate because C++ has a much maligned, and I’d argue misunderstood, feature—the preprocessor. When Bjarne Stroustrop and his friends (http://www.research.att.com/~bs/homepage.html) at Bell Labs designed C++ in the early 1980s, a key goal of the language to ensure its adoption by the large base of C developers was to make it upwardly compatible. That is, I could take a typical C application and compile it as C++ as-is. Then I could add support for C++ features such as classes, inheritance, and so on as time permitted. One of the features of C that had to be retained was the preprocessor. Now some argue that the preprocessor in C is a relic similar to the goto statement and should not be used since there are reasonable replacements such as templates (whether classes or functions).

But the point I think is missed—the preprocessor is easy to use, fast, and most C/C++ developers are familiar with it. It also can make your code easier to read, although a bit harder to debug—if a macro is poorly written, you might find the debugging part a nightmare. But again, it comes down to how well thought out the macro is and how limited its scope is. Basically the macro should be easy to understand when you see it used in code, and it should hide unnecessary detail.

Here’s a macro that can be used to handle rich error information from a COM object:

#define CHECK_HRESULT_THROW( call,iface ) \
    { HRESULT _hr = (call);  \
    if ( FAILED(_hr) ) \
    { \
        CComQIPtr<ISupportErrorInfo> pError( iface ); \
        if ( pError ) \
        { \
            hr = pError->InterfaceSupportsErrorInfo(  \
                        __uuidof(iface) ); \
            if ( hr == S_OK ) \
            { \
                CComPtr<IErrorInfo> pErrInfo; \
                hr = ::GetErrorInfo(0, // reserved; must be zero \
                            &pErrInfo); \
                if ( hr == S_OK && pErrInfo ) \
                { \
                    throw new com_richerror_exception( pErrInfo ); \
                } \
            } \
        } \
    } }

It’s the same block of code we looked at in the last issue—if the HRESULT of the method we’re calling returns a failure code, we ask the object if it supports rich errors, and then acquire any rich error information it may have.

What’s new is the following line of code:

    throw new com_richerror_exception( pErrInfo );

When a rich error interface is retrieved from an object, the question becomes “how do I handle the error in my code?” In this example, I made the decision to throw an exception derived from the Standard Template Library std::exception class that takes an IErrorInfo interface in its constructor. This exception can then be handled by a local try/catch block or one higher up the call stack. Another option is to use the Native C++ compiler support and the function _com_issue_errorex, which will issue a _com_error type exception. But not all developers wish to use both ATL and Native C++ COM support in the same object, so I will focus on doing this in ATL.

A word of caution: Never throw a C++ style exception out of a COM method. It’s bad form and unlikely to be handled properly by callers. COM defines HRESULTs as the normal form of returning errors (although some also return [out,retval] values as more advanced errors).

So to use this macro in some code, it might look like this:

    void MyFunction()
    {
        CComPtr<ISomeInterface> pI;
        pI.CoCreateInstance(__uuidof(SomeObject));

        try
{    
            CHECK_HRESULT_THROW( pI->SomeMethod(),pI );
        }
        catch( com_richerror_exception& err )
        {
            CComPtr<IErrorInfo> pErr( err.errorInfo );

            // do something with the error.
        }
    }

If the method that you needed to call had parameters, the code in bold would change slightly:

    void MyFunction()
    {
        CComPtr<ISomeInterface> pI;
        pI.CoCreateInstance(__uuidof(SomeObject));

        try
{    
            CHECK_HRESULT_THROW( pI-><code>SomeMethod(Parm1,Parm2),pI</code> );
        }
        catch( com_richerror_exception& err )
        {
            CComPtr<IErrorInfo> pErr( err.errorInfo );

            // do something with the error.
        }
    }

When the macro expands, all of that nice error handling is added to this code, yet doesn't clutter up the readability of it. Also, if I want to modify how I handle rich errors in my application, I can modify the macro in one spot rather than changing this code all over the place. I can also enforce standard behavior such as logging the exception to an external log file for debugging purposes.

This is not the only way this macro could have been written. However it was simple, fast, and straightforward to put together. As I said earlier, debugging it can be a pain, so I usually manually expand code like this in a module and debug it thoroughly before moving to a macro. Once it’s working though, using it is a snap.

A downside to the macro approach is the bulk it adds to your code. Since the rich error handling is expanded each time it is used, the compiled code size can increase quite a bit and will impact the application’s memory usage and quite possibly its performance.

But not to fear—we can make a final change to the macro implementation to further optimize it without losing the usability of the macro. We’ll hide the implementation inside of a template function. Template functions are the less appreciated cousins of template classes, but they do have useful purposes, particularly when you have a function that offers the same behaviors for different types of data.

Reworking our macro implementation into a template function, here’s how that would look:

    template <class T>
    void COMRichErrorHandler( T* p ) throw(...c)
    {
        CComQIPtr<ISupportErrorInfo> pError( p ); 
        if ( pError ) 
        { 
            hr = pError->InterfaceSupportsErrorInfo(  
                        __uuidof(T) ); 
            if ( hr == S_OK ) 
            { 
                CComPtr<IErrorInfo> pErrInfo; 
                hr = ::GetErrorInfo(0,&pErrInfo); 
                if ( hr == S_OK && pErrInfo ) 
                { 
                    throw new 
com_richerror_exception( 
pErrInfo ); 
                } 
            } 
        } 
    }

The same code as before, but now it expands once per type T rather than each time it is used (like the earlier macro approach). However, we can still use this template function with the macro to give us the best of both:

#define CHECK_HRESULT_THROW( call,iface ) \
    { HRESULT _hr = (call);  \
    if ( FAILED(_hr) ) COMRichErrorHandler( iface ); }

With this new macro, the template function is expanded for the type of the interface that is passed to the macro. So instead of many lines of code expanded every time we use the macro, we have just a few.

Up to now we’ve been looking at how to handle rich errors from COM objects. The natural next question (to me at least) is how to report the errors from a COM object in the first place. If you’re a fan of ATL, you’re in luck because ATL makes this a snap to do.

The first step in ATL is to add support for the ISupportErrorInfo interface—I mentioned in the first article in this series that this can be done easily when you create a new simple object in ATL using the ATL wizard and checking the ISupportErrorInfo checkbox. This will add code into your COM object that enables the object to report back to callers that it supports rich errors.

The next step is to actually report rich errors when they occur. To do this, ATL provides the AtlReportError function. This function takes the UUID of the interface that is reporting the error (usually the primary interface of the object), the CLSID of the object itself, and a description. There are variants of this function that also provide support for passing through a help filename and topic ID if this makes sense for your application. Behind the scenes, ATL creates an instance of an error object that supports the IErrorInfo object using the CreateErrorInfo SDK function. The error object is then stored using the SetErrorInfo SDK function, which stores the interface for the current thread. If a subsequent error occurs and it generates another rich error, the current rich error is discarded (released) and the new one takes its place.

Be aware of a gotcha with using the AtlReportError function, though—you cannot use this function within the catch block of a try/catch because it uses some stack-based memory allocation functions that won’t work properly in a catch block. Here is the warning from Microsoft:

"Caution: Do not use AtlReportError in C++ catch handlers. Some overrides of these functions use the ATL string conversion macros internally, which in turn use the _alloca function internally. Using AtlReportError in a C++ catch handler can cause exceptions in C++ catch handlers."

This is one of those lovely notes they put at the bottom of the help, where you probably would miss it if you didn't take time to read the whole set of documentation for the function. I almost missed it myself the first time I read through the text.

That’s it for reporting errors. It’s simple enough that you can wrap the reporting itself into some macros of your own to simplify the process of filling out the CLSID and interface UUID parameters as well as loading messages from something like string resources or external stores.

I hope after reading through this series, you see that it’s simple to add rich error support to your COM objects if you spend some minimal time developing some coding support to make it easier. Although it’s a little more work than not doing it, the users of your COM objects, particularly in scripting languages or Visual Basic, will appreciate it.


Mark M. Baker is the Chief of Research & Development at BNA Software located in Washington, D.C.
Do you have a Windows development question? Send it to [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.