Thursday, October 13, 2011

error contract functions

In code for Windows there are many places that are boundaries across which C++ exceptions cannot safely travel.

The most accurate way to describe the boundaries is to say that C++ exceptions cannot be sent across modules (exe and all and sys are all examples of modules). It may not be completely clear from that statement when a module boundary is crossed. Here are some examples that should help reason about when a module boundary is being crossed. The most straight forward situation is when a function is exported from a dll so that it can be called from a different module. but it can get considerably more abstract when you think of dll methods that return COM interfaces or structs containing function pointers. Then add all the PROC's used in Windows programming. WNDPROC, DLGPROC and so many other PROC's are all functions in one module called from a different module.

Any C++ exceptions that occur in these boundary functions must be stopped before the calling module try's to process them. The problem is that exception types are not always the same in each module. if the calling module try's to process an exception from a different module it could be that the type has different members or that the size of the members is different and the constructors, destructors and other methods compiled into the calling module are the wrong methods for an instance of the type that was constructed in the called module. Crashes or data corruption will ensue.

When building all these boundary functions it becomes important to centralize the catch blocks used to convert exceptions to errors and the FAIL_FAST for uncaught exceptions. Without centralization there is a lot of duplicated code.

With lambdas and perfect-forwarding using r-value references we can build error contract functions that will centralize the boundary contract for errors in one function and apply that contract to many functions. Here is an implementation of a error contract function for use with boundary functions that return HRESULT.

template<typename Function>
HRESULT HResultErrorContract(Function && function)
{
  HRESULT result = S_OK;
  FAIL_FAST_ON_THROW(
  [&] {
    // any C++ exception that is uncaught or any SEH
    // will cause the process to exit
    try
    {
      unique_hresult hresult = (
        std::forward<Function>(function)());
      result = hresult.get();
    } catch (const std::bad_alloc&) {
      result = E_OUTOFMEMORY;
    } catch (const unique_winerror::exception& e) {
      result = HRESULT_FROM_WIN32(e.get());
    } catch (const unique_hresult::exception& e) {
      result = e.get();
    }
  }
  );
  return result;
}

HRESULT MyDllExport()
{
  // no exception can get past
  // HResultErrorContract
  return HResultErrorContract(
  [&]() -> unique_hresult {
    // It is safe to throw C++ exceptions
    unique_hresult hresult;
    hresult = Work();
    return hresult;
  }
}

error contract functions are specific to the module they are used in, they are not candidates for a general shared template library. This is because each module uses different libraries and therefore has different exceptions that must be caught and translated.

No comments:

Post a Comment