3

I'm currently writing an audio application using WASAPI, which is a really great learning experience, however I have this odd problem, I don't really know what to do about all these HRESULT codes in a good way and im looking for alternatives to my current approach which is if_else chains.

Since the MSDN documentation mentions that "the error codes are not limited to what we list in these tables" I feel like I have no choice but to be a good citizen and check each single function call for failure and then if it fails do a cleanup and quit the program.

This leads to an obscene amount of error handling code though. I've been considering just using exceptions however I have this prejudice against them due to coming from a game development background where I've been properly indoctrinated to treat them as hellspawn bloating up the program and degrading performance, so I'm relunctant on that front.

What's a good way of dealing with large amounts code that returns HRESULTs in the sense of making the code easy to maintain and read? (preferably something else than the massive if_else chains I've got now)

  • How important is it that your application gracefully survive erroneous events? – whatsisname Jun 4 '17 at 23:02
  • @whatsisname Not at all, if something goes wrong (and it's not me misusing the API) then it's most likely out of my hands, i.e out of memory or some external error. – vlind Jun 4 '17 at 23:08
  • 2
    Are you familiar with the FAILED(hr) macro (MSDN)? – rwong Jun 5 '17 at 3:16
  • An out of memory error is not something that is "out of your hands." It's perfectly possible to gracefully deal with such a situation without exiting the app. – user1118321 Jun 5 '17 at 3:54
  • @user1118321 Only if you know what you're doing. If you're not careful you can create some very undesireable side-effects. – Neil Jun 5 '17 at 8:46
2

I usually wrap HRESULTs that indicate failure into std::system_error exceptions.

This goes a long way to clean up the code. The return value will be available for the actual data processed by the functions and error handling is separated from program logic (at least for the caller of your functions).

I find it especially useful that I can add context information to exceptions that allows me to trace back where the error originated from. Most often when we write a function we have to make a chain of API calls. In C code we would simply return the HRESULT from our function, but the context would be lost.

I've been considering just using exceptions however I have this prejudice against them due to coming from a game development background where I've been properly indoctrinated to treat them as hellspawn bloating up the program and degrading performance

Performance is only degraded for the exceptional code path. Knowing this, exceptions should not be used in cases where failure happens equally as often as success. Then it's no longer an exception and the error code should be returned normally.

I have a simple function that I call for the return value of functions where I don't expect failure during normal operation:

// Throw a std::system_error if the HRESULT indicates failure.
template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
    if( FAILED( hr ) )
        throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}

The compiler will most likely inline it so there won't be any overhead for a function call in the non-exceptional case.

I use it like this with context information in form of a string literal...

ThrowIfFailed(
    SomeApiThatReturnsHRESULT(),
    "Context information for this API" );

... or with context information in form of std::string:

ThrowIfFailed(
    SomeApiThatReturnsHRESULT(),
    "Context information for this API" + std::to_string( additionalErrorInfo ) );

Here is a complete example.

You may derive your own exception classes from std::system_error to further categorize the errors, for instance:

struct WASAPI_error : public std::system_error
{
    // Inherit constructors.
    using std::system_error::system_error;
};
-1

Some general tips:

  • Do NOT handle errors that are not expected. Meaning, should you receive a HRESULT that isn't included amongst your possible scenarios, make sure that after cleaning up, the program is closed and the error code is logged. You'll thank me later.
  • FAILED can at least tell you the gravity of the result, so if it is not an error, but you don't know what code represents, you can still log it for later without closing the program.

At this point, if it is an error that is expected that you can deal with, you can create an abstract class ErrorHandler with undeclared methods canHandle(HRESULT) and handle(HRESULT). Ideally you would have an extended class of ErrorHandler for each different response to an error, with canHandle responding true to all the specific HRESULT values that get handled in the same way. You can of course modify the method signature of handle and canHandle to accompany additional pointers to controllers that allow the handler to perform various actions as necessary.

You'd then have a factory that will return an ErrorHandler that handles the particular HRESULT passed, and you'd only have to call handle with the appropriate parameters. If there is no ErrorHandler to fit the HRESULT, you can handle this in one of two ways. One is to check that the ErrorHandler returned from the factory is non-null, and leave handling an unexpected error to the caller, but a cleaner solution would be to simply have an ErrorHandler specifically to deal with this case within the Factory itself (Factory checks if return value is null, and if so, returns an instance of a UnexpectedErrorHandler so that there is always an ErrorHandler, even if the ErrorHandler is to deal with an unexpected error). Just be sure in this case that again, the program is responsibly closed and the error code is logged.

This solution is a very clean error handling system which lets you handle new errors with relative ease. There's nothing stopping you from extending existing ErrorHandler classes and performing actions in addition to the ones performed in the parent class.

Hope that helps!

  • 1
    There are many cases where it might not be your code (the one that received the HRESULT) that should be responsible for gracefully failing the application. This is especially true if you are providing a library that is loaded in a larger system. From your point of view (the library) the failure might be fatal, but to the larger system it might be trivial and would not threaten its stability or consistency. The best you can do is to report the error to the caller in some way (exceptions are a good way but not always available) and let them decide if it warrants more drastic measures. – Newtopian Jul 11 '17 at 14:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.