Exception Boundaries: Working With Multiple Error Handling Mechanisms

Exception Boundaries: Working With Multiple Error Handling Mechanisms

  • Comments 12

David Blaikie

Greetings! I'm David Blaikie, Software Design Engineer at Microsoft and guest blogger here on VCBlog. I work in the Windows product group, writing test infrastructure tools in C++. One of the luxuries of the codebases I work in is that they are relatively modern and flexible. We use the full gamut of C++0x features implemented in Visual Studio 2010 including lambdas and rvalue references/move construction where possible. At a more fundamental level, we also choose to use exceptions.

Though I won’t get into a debate on the pros and cons of different error handling techniques in this post, it suffices to say that we use exceptions but both our dependencies in some cases (WinAPIs, etc) and some of our users (we have API level exposure to Windows test code sources) don’t expose exceptional APIs or build their code with exceptions enabled. To that end, we, like many other developers, need to live our exceptional lives within a possibly unexceptional world. Perhaps you’ve encountered similar situations and thought that because the rest of your code base (or your dependencies/consumers) weren’t using exceptions that your own code was just going to have to inherit that design choice.

In fact, there are a variety of simple techniques, reusable code snippets and small library classes you can write to happily insulate your exceptional code from unexceptional consumers and dependencies.

 

Unexceptional Dependencies

Dealing with dependencies (API functions, classes) that do not use exceptions is fairly simple. Let’s first take an example of some simple unexceptional code and see how it might be transformed:

  1. BOOL DiffHandles(HANDLE file1, HANDLE file2);
  2. BOOL DiffFiles(const wchar_t* file1, const wchar_t* file2)
  3. {
  4.     HANDLE file1Handle = CreateFile(file1, GENERIC_READ, ...);
  5.     BOOL result = FALSE;
  6.  
  7.     if (file1Handle != INVALID_HANDLE_VALUE)
  8.     {
  9.         HANDLE file2Handle = CreateFile(file2, GENERIC_READ, ...);
  10.         if (file2Handle != INVALID_HANDLE_VALUE)
  11.         {
  12.             result = DiffHandles(file1Handle, file2Handle);
  13.             CloseHandle(file2Handle);
  14.         }
  15.         CloseHandle(file1Handle);
  16.     }
  17.     return result;
  18. }

First things first: Don’t use this function. It’s good as a demonstration, but as real code it has a bunch of things wrong with it (not to mention it’s incomplete anyway).

While this code doesn’t seem to have a lot of error handling problems, that’s only because the error message handling is done off to the side. CreateFile, for example, specifies that if it returns INVALID_HANDLE_VALUE it will provide additional error information through GetLastError and we could imagine that DiffHandles would have to do the same thing, seeing similar failures from reading from the file (if a network share went away, for example) and would heap those all under a FALSE return value. Users of DiffFiles would have to ensure they check GetLastError any time that FALSE is returned to them. Not only would it be easy for them to forget and just assume the files were different, rather than that the comparison itself failed (perhaps it’s a transient network issue – the file comparison failed and then the network connection came up again and the files happen to have matching contents. This could cause problems) but also the failures the user of this API receives are vague and any messages shown to the user would be hard for the user to understand or address. While the error value from the function might say that a file was [not found, unable to be read because of permissions, or any other reason] it might not say /which/ of the two files had the problem.

So our first step might be to ensure actual failures produce exceptions while the test (are the file contents different) returns false. This will distinguish between the legitimate cases and runtime failures that occurred while attempting to execute the function. In this case our function’s type remains the same (though I’ll switch to “bool” now that we don’t have a Win32 legacy to interact so directly with) though the contract is different: Return true if the files match, false if the files differ, throw an exception if we couldn’t determine whether the files match or not.

We’ll introduce a simple helper function to that end:

  1. void ThrowLastErrorIf(bool expression, const wchar_t* message)
  2. {
  3.     if (!expression)
  4.     {
  5.         throw Win32Exception(GetLastError(), message);
  6.     }
  7. }

This isn’t the most advanced form of such a function. We could do much more to create more informative/human readable error messages. Perhaps Win32Exception type could use FormatMessage to produce a human readable message from the error code and append our message string on to that in some manner. In any case, our function can now be rewritten as follows:

  1. bool DiffFiles(const wchar_t* file1, const wchar_t* file2)
  2. {
  3.     HANDLE file1Handle = CreateFile(file1, GENERIC_READ, ?);
  4.     ThrowLastErrorIf(file1Handle != INVALID_HANDLE_VALUE, file1);
  5.     HANDLE file2Handle = CreateFile(file2, GENERIC_READ, ...);
  6.     ThrowLastErrorIf(file2Handle != INVALID_HANDLE_VALUE, file2);
  7.     BOOL result = DiffHandles(file1Handle, file2Handle);
  8.     ThrowLastErrorIf(result == FALSE && (GetLastError() != MY_APPLICATION_ERROR_FILE_MISMATCH), L"Could not compare file contents");
  9.     CloseHandle(file1Handle);
  10.     CloseHandle(file2Handle);
  11.     return result;
  12. }

But wait, I (hope I) hear you cry, wouldn’t this leak file handles if we throw exceptions? Right you are. While I could’ve written this modified version to handle that leak it would’ve been somewhat convoluted so instead I’m going to demonstrate a better way.

By wrapping up these HANDLEs in a type that can handle their destruction in a more C++, RAII manner we can not only make this code more readable but also correct (non-leaking)

  1. class File
  2.     {
  3. private:
  4.     HANDLE handle;
  5.  
  6.     //declared but not defined to avoid double closing
  7.     File& operator=(const File&);
  8.     File(File&);
  9. public:
  10.     File(const wchar_t* file)
  11.     {
  12.         handle = CreateFile(file, GENERIC_READ, ...);
  13.         ThrowLastErrorIf(handle, file);
  14.     }
  15.  
  16.     HANDLE Get()
  17.     {
  18.         return handle;
  19.     }
  20.  
  21.     ~File()
  22.     {
  23.         CloseHandle(handle);
  24.     }
  25. };

And rewriting the original function again, we get:

  1. bool DiffFiles(const wchar_t* file1, const wchar_t* file2)
  2. {
  3.     File f1(file1);
  4.     File f2(file2);
  5.     result = DiffHandles(f1.Get(), f2.Get());
  6.     ThrowLastErrorIf(result == FALSE && (GetLastError() != MY_APPLICATION_ERROR_FILE_MISMATCH), L"Could not compare file contents");
  7.     return result;
  8. }

All without leaks and the need to pay close attention to code paths to ensure resource destruction.

It’s not quite the same as it would be if DiffHandles was an exception-aware API, but it tidies up the function a bit and means that unexceptional dependencies don’t pollute our exception-aware codebase.

The implementation of the Win32Exception type and enhancements to the ThrowLastErrorIf function (to include mapping specific result values to already well known exception types such as std::bad_alloc) is left as an exercise for the reader.

 

Unexceptional Consumers

Types of Unexceptional Consumers

We’ve seen that Win32 “invalid return (FALSE, INVALID_HANDLE_VALUE, etc) + GetLastError” is one kind of unexceptional error message scheme. Other APIs you might run into that have an unexceptional boundary include C code (indeed Win32’s API is a specific case of this, but POSIX uses int return values and errno to similar effect) or HRESULT returning COM APIs.

[While it might not be immediately obvious why it’s worth considering C code as an unexceptional consumer (“my users will be writing in C, so my library must be in C” I hear you cry) it’s actually quite possible to write a C API in C++. By declaring your functions with extern “C” you can have C linkage functions in a C++ compilation unit using the full functionality of the C++ programming language in your implementation]

 

Dealing with Unexceptional Consumers

Dealing with unexceptional consumers is perhaps a little trickier, though the most basic implementation is not terribly difficult, if a little verbose and lossy. Let’s invert the above example. Imagine we had the original DiffFiles, but we wanted to keep the interface (BOOL do_things + GetLastError) but we had updated our dependencies (File handling using RAII resource wrappers as shown, as well as updating DiffHandles, or using the STL) to be exception-aware themselves. As such they no longer return failures through GetLastError, instead returning bool and throwing exceptions for their failures. Perhaps not even Win32Exception failures (this particular exception type wouldn’t be used pervasively, but only when interacting with unexceptional APIs where no more accurate exception type was available to represent the failure).  We could simply rewrite the DiffFiles function as follows:

  1. BOOL DiffFiles(const wchar_t* file1, const wchar_t* file2)
  2. {
  3.     try
  4.     {
  5.         File f1(file1);
  6.         File f2(file2);
  7.         if (!DiffHandles(f1, f2))
  8.         {
  9.             SetLastError(MY_APPLICATION_ERROR_FILE_MISMATCH);
  10.             return FALSE;
  11.         }
  12.         return TRUE;
  13.     }
  14.     catch(const Win32Exception& e)
  15.     {
  16.         SetLastError(e.GetErrorCode());
  17.     }
  18.     catch(const std::exception& e)
  19.     {
  20.         SetLastError(MY_APPLICATION_GENERAL_ERROR);
  21.     }
  22.     return FALSE;
  23. }

You should be sure to catch any/all exceptions which could be produced by the code in the try block. In this case we know that the File class and DiffHandles function can only throw Win32Exceptions so we can just handle that.

With this basic implementation we lose all exception detail, even those details we could map to interesting results (perhaps std::bad_alloc could be mapped to an out of memory Win32 error code for example), so it’s not ideal. Again, we could imagine putting a variety of catch blocks in to map different exception types to various failures, adding logging to record the full details of the exception (since we’ll be compressing entire exception objects including strings of context, stack traces, etc, into a single win32 error code) before it is coalesced into a single value for return, etc. In doing so every one of the functions on our unexceptional public interface is going to get long and unwieldy.

 

Macros as an Exception Consuming Boundary

To reduce the syntactic overhead in this case we can use macros to implement a convenient wrapper to hide all that possible complexity and repeated logic:

  1. #define WIN32_START try {
  2. #define WIN32_END } catch (const Win32Exception& e) { SetLastError(e.GetErrorCode()); } catch (const std::exception& e) { SetLastError(MY_APPLICATION_GENERAL_ERROR); } return FALSE;

The do_things function then becomes:

  1. BOOL DiffFiles(const wchar_t* file1, const wchar_t* file2)
  2. {
  3.     WIN32_START
  4.         File f1(file1);
  5.         File f2(file2);
  6.         if (!DiffHandles(f1, f2))
  7.         {
  8.             SetLastError(MY_APPLICATION_ERROR_FILE_MISMATCH);
  9.             return FALSE;
  10.         }
  11.         return TRUE;
  12.     WIN32_END
  13. }

While macros provide an obvious way to implement this functionality they can make code hard to debug and analyze.

 

Lambdas as an Exception Consuming Boundary

We can tidy this up a little further, replacing macros with lambdas as follows:

  1. template<typename Func>
  2. BOOL Win32ExceptionBoundary(Func&& f)
  3. {
  4.     try
  5.     {
  6.         return f();
  7.     }
  8.     catch(const Win32Exception& e)
  9.     {
  10.         SetLastError(e.GetErrorCode());
  11.     }
  12.     catch(const std::exception& e)
  13.     {
  14.         SetLastError(MY_APPLICATION_GENERAL_ERROR);
  15.     }
  16.     return FALSE;
  17. }

With this function, we can now reduce our do_things() function to:

  1. BOOL DiffFiles(const wchar_t* file1, const wchar_t* file2)
  2. {
  3.     return Win32ExceptionBoundary([&]() -> BOOL
  4.     {
  5.         File f1(file1);
  6.         File f2(file2);
  7.         if (!DiffHandles(f1, f2))
  8.         {
  9.             SetLastError(MY_APPLICATION_ERROR_FILE_MISMATCH);
  10.             return FALSE;
  11.         }
  12.         return TRUE;
  13.     });
  14. }

The Win32ExceptionBoundary could be generalized (so it could be used with, say, HANDLE returning functions) by taking the error result as an extra parameter and using that to infer the return type of the template function, for example.

 

Summary

With tools such as these you can introduce exception-aware code into your code base, enabling you to take advantage of the myriad of carefully implemented and tested standard libraries such as containers, smart pointers, and algorithms without having to revamp your entire codebase. Your exception-aware walled garden can grow as time and business justification permits, converting single functions/libraries at a time.

  • One thing I've seen when dealing with unexceptional dependencies that return error codes (such as an HRESULT) is to create something like this:

    struct HRThrower {

      void operator=(HRESULT hr) {if(FAILED(HR)) throw SomeException(hr);}

    };

    Then, your code can look something like this:

    void func() {

      HRThrower hr;

      hr = foo();

      hr = bar();

    }

    I haven't figured out a good way to handle unexceptional dependencies that return error codes in an out parameter.  At least, not anything that is an improvement over the ThrowLastErrorIf function presented in the article.

  •    File& operator=(const File&) = delete;

       File(File&) = delete;

    Naughty. When do we get the support for this C++0x feature? It cannot be soon enough!

  • This is one of the best articles I've seen on this blog.  I've tried before to convince coworkers that you can migration to exceptions without rewriting the entire codebase at once.  Perhaps I can use some of your examples to bolster my argument.

    Boundaries can exist in some surprising places.  COM boundaries, for example.  Your COM object implementation might use exceptions internally, but it must be sure to no leak them out to the caller.  There's no marshaling for exceptions.

    Also, if your code is called back from unknown libraries that might be written in C, you probably shouldn't throw an exception and expect it to be safely caught by code on the other side of the callback mechanism.  For example, if you throw an exception from a window procedure, it's not safe to assume that it'll reach a catch block in your message pump.  You've got unknown Windows code in there (user32.dll) that was probably written in C.  There's no guarantee that C++ exceptions will propagate correctly through the C code.  Although I suspect it'll work on Windows with code compiled with Visual Studio, it's not supported by the standards and might be subject to change in the future.  Even if the exception does arrive, the C-based code can't use RAII and thus it's likely to leak resources.

    (Most of your later examples should call DiffHandles(f1.Get(), f2.Get()), assuming you didn't intend to redefine DiffHandles to take file objects.)

  • File(const wchar_t* file)

       {

           handle = CreateFile(file, GENERIC_READ, ...);

           ThrowLastErrorIf(handle, file);

       }

    The destructor does not get called if an exception is thrown from constructor. if the Handle is a Smart pointer, it is fine otherwise there is a leak here.

  • @Jagannath

    Erm, if i read the post correctly, ThrowLastErrorIf will only throw if CreateFile fails so it would throw when no valid handle is returned. Since the constructor doesn't allocate anything else then there is no leak.

    But there is still a problem with that part. CreateFile returns INVALID_HANDLE_VALUE on failure (-1) but ThrowLastErrorIf checks for 0. So it won't throw in this case.

  • Your last example seems invalid, if the lambda is more than a single line you have to specify the return type, otherwise it defaults to void.

    The code should read <code>[&]() -> bool</code>

  • Nice post. But why should I invent a wheel if there are ready solutions, like ATL::CHandle and _com_error ?

    My code would look like this:

    #include <Windows.h>

    #include <comdef.h>

    #include <atlbase.h>

    BOOL DiffFiles(const wchar_t* file1, const wchar_t* file2)

    {

       BOOL result = FALSE;

       BOOL gotToComparison = FALSE;

       do

       {

           // We have RAII here

           ATL::CHandle file1Handle = CreateFile(file1, GENERIC_READ, ...);

           if (INVALID_HANDLE_VALUE == fileHandle)

           {

               file1Handle.Detach(); // ATL::CHandle consider INVALID_HANDLE_VALUE as a valid value.

               break;

           }

           // And we have RAII here

           ATL::CHandle file2Handle = CreateFile(file2, GENERIC_READ, ...);

           if (INVALID_HANDLE_VALUE == file2Handle)

           {

               file2Handle.Detach(); // ATL::CHandle consider INVALID_HANDLE_VALUE as a valid value.

               break;

           }

           gotToComparison = TRUE;

           result = DiffHandles(file1Handle, file2Handle);

           if(!result)

           {

               SetLastError(MY_ERROR_CODE);

           }

       }

       while(false);

       if(!gotToComparison)

       {

           _com_issue_error(HRESULT_FROM_WIN32(GetLastError()));

       }

       return result;

    }

    int _tmain(int argc, _TCHAR* argv[])

    {

       try

       {

           BOOL differ = DiffFiles(L"c:\\a.txt", L"c:\\b.txt");

           // some logic

       }

       catch(const _com_error& ex)

       {

           // And here we have a proper error message.

           MessageBox(NULL, ex.ErrorMessage(), NULL, MB_ICONERROR);

       }

       catch(...)

       {

           // Handler

       }

       return 0;

    }

  • ATL is a big dependency to pull in just to get a file handle RAII wrapper.

  • Hi David,

    As you said, you can work in a modern and flexible code.

    Is there any reason to use BOOL instead of bool? (I know that in some cases there is a performance warning, and also the windows uses BOOLs everywhere) What is the recomendation?

    BOOL DiffFiles(const wchar_t* file1, const wchar_t* file2)  { ... }

    thanks

  • @Alexander: Basically the point was to demonstrate the techniques - it's a difficult balance between showing something real-world enough to motivate the examples while still demonstrating the mechanics. Certainly in some cases there are existing tools that are better than rolling your own (& we should keep these in mind), but understanding the general approach is helpful when you move beyond those cases or don't want to/can't take a dependency on an existing helper.

    @Thiago: For safety it's best to be consistent - if an API gives you BOOL, you need to be aware of that & treat it as distinct from bool until you've deliberately translated the value to bool (via == TRUE, for example). I basically tried to use BOOL when I was being Win32-ish, and bool when I was being "modern" and convert between the two when my consumer/dependency differ in their preference.

  • There's some details/discussion on the differences between BOOL and bool here: social.msdn.microsoft.com/.../1646b1fc-dae3-4818-a971-ba1f7e599eef

  • For our ATL COM code we use a technique similar to the lambda based try/catch block, but then C++03 style.

    All our COM entry points look like this:

    STDMETHODIMP MyClass:Func()

    try

    {

       // Exception based Func() implementation here..

       return S_OK;

    }

    catch (...)

    {

       return HResultFromException();

    }

    The HResultFromException() function lives in a library and implements an exception filter:

    HRESULT HResultFromException()

    {

       try

       {

           throw;

       }

       catch (std::bad_alloc&)

       {

           return E_OUTOFMEMMORY;

       }

       catch (std::out_of_range&)

       {

           return E_INVALIDARG;

       }

       catch (CAtlException& e)

       {

           return e;

       }

       // Many more...

       catch (...)

       {

           return E_UNEXPECTED;

       }

    }

    There is extra code to also set the IErrorInfo from the information in the exception object.

    For COM calling code, we have a CheckedHResult class that throws when initialized or assigned with an error HRESULT. Finally, there is a std::bind() like binder for COM calls that works like this:

    interface IBar : IUnknown

    {

       HRESULT GetFoo(int arg, [out, retval] IFoo** ppIFoo);

    }

    CComPtr<IFoo> pIFoo = ComCall(pIBar, &IBar::GetFoo)(12);

    The ComCall object translates HRESULTs into exceptions and returns retval arguments as normal return values. It manages refcounts correctly by wrapping the return object in the proper ATL RAII class for CComPtr's, CComBSTR's, CComVARIANT and CComSafeArrays.

    ATL has become much more programmer friendly ever since we developed this exception infrastructure.

    Gert-Jan

Page 1 of 1 (12 items)