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

Lookup and Display Win32/COM Error Strings With One Line of Code

0.00/5 (No votes)
31 Dec 2005 1  
Use compiler COM support (even in non-COM applications) to get a Win32 error code or HRESULT's message in one line of code.

Figure 1. The SimpleErrors sample application for this article.

Introduction

In your previous project, you may have called Win32 or COM functions. This article won't discuss the particulars of calling such functions, but instead my purpose is to reveal a really simple way to handle errors from these functions. Mostly the functions themselves only return a hard-to-parse HRESULT (from COM) or an invalid HANDLE value (such as INVALID_HANDLE_VALUE or some other such value). But it's not easy to get the error that actually occurred, so that one can just display the message to the user.

Other articles on the Code Project, such as Ajit Jadhav's article, "Do Not Call GetLastError()!", and others, advocate a simple, object-oriented approach to handling this ugly situation. It turns out there's an even simpler approach, and one that will look up the error value, and parse it, and display the corresponding string (if found) in a message box, and all in one line. It has to do with compiler COM support, which - as it turns out - you can use even if you're not using COM anywhere in your application.

Do what makes sense for your application

Several posters to this article (below) have made very insightful comments and I welcome any and all feedback on this approach. Some (not meant to be inclusive) to using this approach, and _com_error in particular, are listed in the message board posts below.

This article is not advocating this approach to the exclusion of all others, nor is it saying there aren't other approaches which are better. But I am just putting this out here, especially for some of you who aren't in the mood to write big switch/case or if/else blocks every time you call a Win32 function, but instead want to do some more advanced handling.

Basically, the message is: Here's another feature of VC++ that may be helpful to you; but in the end, the important thing is to do what makes the most sense for your particular application.

The _com_error class

The details of compiler COM support and the classes it provides are beyond the scope of this article. Needless to say, there's a class, called _com_error, which you can use in lieu of some other ungodly approach, say, by using FormatMessage(). Jadhav tried to work around this by providing a CWin32Error class, which was even throwable as an exception object, which supposedly does what, in fact, the compiler's COM support already gives you!

I will leave looking up _com_error in the docs to you, but needless to say this class has, among its constructors, a constructor that accepts either: a HRESULT or the output of the Win32 function, GetLastError(). Then, you can call the member function, _com_error::ErrorMessage() and get the corresponding system error message, quite naturally. Plus, the _com_error class is itself an exception class, and works with the compiler's built-in exception handling syntax. You can throw, catch etc. blocks using _com_error to your heart's content. Unfortunately, exception handling is not really my bag, so this article looks at only how to use the _com_error::ErrorMessage() member function.

It's clear from the message board posts in Jadhav's article that people are aware of this, but it is in fact quite simple to use, and it works in a proven manner, as the included demo project and source code illustrate. Let me take you on a tour of the particulars of using the _com_error class with ease. Another upside of using _com_error natively is that the system error messages are automatically localized so that they appear in the language that the computer's locale is using.

But don't I have to use COM in order to use compiler COM support?

The short answer: No. Not if the class doesn't depend on such, and thankfully, _com_error doesn't.

Using _com_error in your application

To illustrate how to use the _com_error class in your application, I used the MFC AppWizard to whip up a little, dialog-based sample program called SimpleErrors. As an example, Listing 1 shows the one single line of code to throw up a message box based on a Windows Win32 SDK system error, as returned by GetLastError():

AfxMessageBox(_com_error(GetLastError()).ErrorMessage(),MB_ICONSTOP);

Listing 1: Alerting the user to a Win32 error with a message box.

Listing 2: Shows the single line of code to use to get an error message from a FAILED HRESULT.

AfxMessageBox(_com_error(hResult).ErrorMessage(),MB_ICONSTOP);

Listing 2: Alerting the user to a COM error with a message box.

All of this comes "for free" just with Visual C++! Here are the steps.

Step 1: Add code to STDAFX.H

To start, we need to make sure the proper include files are added to STDAFX.H. If you're not coding an MFC application, then add the line below to whatever file is #included in all your source files. Anyway, the line to add is shown, below, in Listing 3:

#include <comdef.h>    // Compiler COM support

Listing 3: Including the correct header file for compiler COM support and _com_error.

Step 2: Add _com_error calls to your application

In the SimpleErrors sample application, I have added two buttons: one button that makes a bogus Win32 CreateFile() call; and another button to make a bogus CoCreateInstance() call on a non-existent interface (the self-deferential interface, IAmDumb). Let's go on a tour of the message handlers for each button. Listing 4 shows how I set up the bogus Win32 call:

void CSimpleErrorsDlg::OnBnClickedCallFunction()
{
    // Do something bogus here, like try to open a file

    // which doesn't exist.

    if (CreateFile(_T("./testing.TXT"),
        0L,FILE_SHARE_READ,NULL,OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,NULL)==INVALID_HANDLE_VALUE) {
            AfxMessageBox(
              _com_error(GetLastError()).ErrorMessage(),MB_ICONSTOP);
        }
}

Listing 4: Using _com_error::ErrorMessage() to handle a file-opening error.

My approach above was simply to call CreateFile() to open the non-existing file testing.TXT. Notice how the error handling is done all in one line. The line beginning with a call to AfxMessageBox() displays the following message box - shown below in Figure 2 - when the Call Win32 Function And Get Error button, in Figure 1, is clicked:

Figure 2: The call to _com_error::ErrorMessage(), in Listing 4, produced this message box.

When calling a COM function, things work out similarly. Now, with COM, we're calling CoCreateInstance(). For more info on what COM is and how to use COM in your programs, I'll simply refer you to Michael Dunn's excellent introduction on this subject.

Anyway - all the introductions aside - here's how to handle the failure of a COM function. Now, with Win32, we can simply pass the function a filename of a non-existent file, and there we are. But here, we are making up a bogus interface, and interfaces have tags, or GUIDS (Globally Unique IDs) which tell COM which interface you want. The ones we pass to CoCreateInstance() here are the so-called CLSIDs and IIDs. Never mind what these are; again, beyond the scope of this article. My first step, to prepare for calling CoCreateInstance(), is to use the Create GUID tool which comes with Visual C++ to create two GUIDs, one for the CLSID and one for the IID of my fake interface. Here are the ones I generated, shown in Listing 5:

    // {9346460E-F860-450c-B8C6-80D705644FF0}

    static const GUID CLSID_IAmDumb = 
       { 0x9346460e, 0xf860, 0x450c, { 0xb8, 0xc6, 0x80, 
                           0xd7, 0x5, 0x64, 0x4f, 0xf0 } };

    // {5E0E7ED9-83FF-4c31-AD12-46021DE03884}

    static const GUID IID_IAmDumb = 
        { 0x5e0e7ed9, 0x83ff, 0x4c31, { 0xad, 0x12, 0x46, 
                           0x2, 0x1d, 0xe0, 0x38, 0x84 } };

Listing 5: GUIDs I created for the fake, bogus, IAmDumb interface.

If you like, you can just copy the GUIDs from Listng 5 above to your project if you're following along. The best place to put these GUID declarations is in the same source file that I am going to call CoCreateInstance() in.

Note: This is not the way, ordinarily, to go about accessing COM interfaces! See Dunn's article (or the others) above for more details.

Just a reminder to our purpose. Remember, we want to see how to easily trap errors, especially from HRESULTs. So I made up a fake interface, the IAmDumb interface, and then I am going to call CoCreateInstance() on this fake, not-registered interface so that I will get an error to display. See Listing 6, below, for how I do this:

void CSimpleErrorsDlg::OnBnClickedCallCom()
{
    // Call a random COM function, 

    // say, CoCreateInstance(), with

    // bogus parameters so that it 

    // gives us a FAILURE HRESULT

    IUnknown* pUnk=NULL;
    
    // Always must be called before 

    // using COM functions

    CoInitialize(NULL);    

    HRESULT hResult = CoCreateInstance(CLSID_IAmDumb,NULL,
                      CLSCTX_LOCAL_SERVER,IID_IAmDumb,(void**)&pUnk);

    if (FAILED(hResult)) {
       AfxMessageBox(_com_error(hResult).ErrorMessage(),MB_ICONSTOP);
    }

    CoUninitialize();    // Call so Windows can clean up

}

Listing 6. Calling CoCreateInstance() to try and get a fake interface, and handling the resultant error.

Again, I am not here to explain COM to you. Consult the Dunn, above, if you need help with what I am doing. Notice the use of the FAILED() macro. This can be used on any HRESULT value, and is - in general - a good way to determine if your COM call wasn't successful. Look up FAILED in the docs if you want more information. The AfxMessageBox() call above produces the box shown in Figure 3, below:

Figure 3: The result of calling CoCreateInstance() on our fake interface, IAmDumb.

To follow along with me and see this for yourself in the sample application, click the Call COM Function And Get Error button. Again, since the error messages that _com_error::ErrorMessage() provides for you are automatically localized, whatever this message translates to in your locale's language will instead appear (I think).

Discussion

It's worth discussing this particular approach to error-handling, and in particular cases in which it's not a good idea. Above all, you should use whatever approach you think is best for your particular application. A poster to the message board, Doug Scmidt, and to this article, cautions against the temptations to do things in one line. And it's true that there may be application types for which this approach is unsuitable. For more information, see Schmidt's post, below.

As yet another reader mentions that the system error messages provided by _com_error are terse, and rightly so. It's ironic that Microsoft, known for such books as The Windows User Interface Guidelines for Software Design - which sternly lectures readers to make error messages helpful and to not blame the user - would then put into its Windows system error message database these incredibly terse errors.

A possible way to overcome the terseness aspect is to add extra text to the error message; i.e. perhaps invent a GetErrorMessage() function that would format the message in a way that is a little more helpful to the user. I am not going to presume to know how the said function should be implemented, as the implementation for your application's purposes may vary. However, an error message containing the name of the file or interface (or whatever) was being operated on, plus the system error message and a way for the user to resolve the problem might be nice.

Something that is also worth discussing is that: in the Jadhav's article, some message board posters claimed that _com_error::_com_error() needs a HRESULT always, hence you have to pass the output of GetLastError() to it thusly:

AfxMessageBox(
  _com_error(HRESULT_FROM_WIN32(GetLastError())).ErrorMessage(),
                                                    MB_ICONSTOP);

Listing 7: Using the HRESULT_FROM_WIN32 macro on all outputs of GetLastError().

For me personally, even as far back as VC5, this has never been my experience, and _com_error::ErrorMessage() appears to work regardless of whether you use the HRESULT_FROM_WIN32 macro or not.

I could go on and on discussing the shortfalls of this approach, but that is not what I am here to do. Instead, I would be delighted if readers could please use this article's message board, below, to share their thoughts - and tips - for navigating through the waters of Win32 and COM error reporting. Readers are also encouraged to address their posts not to me, but instead to the general readership of this article. Let the message board below be a 'forum' for discussion on this subject.

Conclusion

So there you have it. We looked at how to use a 'built-in' class, the _com_error class, which is provided along with the VC++ compiler, to generate human-readable errors from COM and Win32 calls.

Happy Error Handling (and Happy New Year 2006 - I am writing this on New Year's eve, 2005!)!

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