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)

7
  • How important is it that your application gracefully survive erroneous events? Jun 4, 2017 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, 2017 at 23:08
  • 2
    Are you familiar with the FAILED(hr) macro (MSDN)?
    – rwong
    Jun 5, 2017 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. Jun 5, 2017 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, 2017 at 8:46

3 Answers 3

3

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;
};
4
  • Thanks for the helpful answer. Would also like to add to it's no longer an exception and the error code should be returned normally - at the risk of being pedantic, in the spirit of good encapsulation, it may also be a good idea to wrap this aspect as well by mapping the HRESULT code to a corresponding domain-specific code. But that is entirely up to the design.
    – Bondolin
    Sep 20, 2021 at 16:01
  • @Bondolin In code that adds another abstraction level (i. e. is not just a thin wrapper for an existing API), I catch exceptions from lower abstraction levels, create a new exception that is specific to my API and wrap the catched exception as a nested exception. This way users of my library are able to handle errors on a higher level but there is no loss of information compared to transforming HRESULT to a domain-specific code.
    – zett42
    Sep 21, 2021 at 11:51
  • Indeed, I commend this practice. I think I may have miscommunicated. I understand there to be, on a per-API basis, two sets of HRESULT errors: those communicating exceptional behavior, and those merely indicating some kind of successful but negative result that would not be regarded an error per se. The former I would wrap with exceptions, as you have said; the latter would be those I wrap with a mapping to my own domain-specific code, to encapsulate away the aspect of dealing with an HRESULT.
    – Bondolin
    Sep 21, 2021 at 19:40
  • Note that your context information is a bit inefficient because you are constructing the string on every call, even the ones that don't fail. You could avoid this by passing a lambda that returns the context information.
    – user253751
    Oct 22, 2021 at 11:47
0

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
  • 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, 2017 at 14:23
0

I'm converting HRESULT and DWORD last errors from WinApi to C++ exception with the help of the exceptxx library.

The resulting code looks simple, readable and provides enough error information for logging and troubleshooting:

Response Connection::sendRequest(const wstring& verb, const wstring& url, const map<wstring, wstring>& headers, const string& data)
{
    wstring fullUrl = m_server + url;

    CComVariant varFalse(false);
    CHECK_HRESULT(m_request->Open(CComBSTR(verb.c_str()), CComBSTR(fullUrl.c_str()), varFalse));

    for_each(begin(headers), end(headers), [&](const auto& i)
    {
        CHECK_HRESULT(m_request->SetRequestHeader(CComBSTR(i.first.c_str()), CComBSTR(i.second.c_str())));
    });

    CComVariant requestData(data.c_str());
    CHECK_HRESULT(m_request->Send(requestData));

    ...
}

wstring getUserDistinguishedName(const wstring& userPrincipalName)
{
    ULONG nameSize = 0;        
    THROW_LAST_WIN32_IF(!TranslateName(userPrincipalName.c_str(), NameUserPrincipal, NameFullyQualifiedDN, nullptr, &nameSize));

    wstring distinguishedName(nameSize, L'\0');
    THROW_LAST_WIN32_IF(!TranslateName(userPrincipalName.c_str(), NameUserPrincipal, NameFullyQualifiedDN, distinguishedName.data(), &nameSize));
    distinguishedName.pop_back();

    return distinguishedName;
}

If exception is triggered one can query what() from it and get its description, code, type and location:

Text: The system cannot find the file specified
Error: 2 (WIN32)
Source: main:12

Disclaimer: I'm the library author.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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