6

I'm writing on C++. Most of my code throw exceptions when fail. Sometimes I have to deal with the Window API that is C-oriented and does not throw. So, every time I call a WINAPI function I check the return value and if it indicate an error I use GetLastError() to retrieve the concrete error code. Then I convert that error code in to an error string and throw an exception based on it.

For example:

HANDLE ph = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);

if (!ph) {
    throw std::runtime_error(win_error_to_string(GetLastError()));
}

I was wondering if it is appropriate to write an generic wrapper that invoke a WINAPI function an throw exception if an error was set during the call. Something like that:

template <typename R, typename... Args>
decltype(auto) call_winapi(R(WINAPI*func)(Args...), Args... &&args)
{
    SetLastError(ERROR_SUCCESS);
    const R result = func(args);
    const DWORD error = GetLastError();
    if (error != ERROR_SUCCESS) {
        throw std::runtime_error(win_error_to_string(error));
    }
    return result;
}

The advantage of that technique is that I do not have to check the return value after each WINAPI call, assuming that the result is correct if the function does not throw.

HANDLE ph = call_winapi(OpenProcess, PROCESS_QUERY_INFORMATION, FASLE, pid);

But I'm afraid I missed something. Like, is it always true that if the WINAPI function set an error code that differ from ERROR_SUCCESS that means that the function failed?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
draganm17
  • 89
  • 1
  • 2
  • you may want to check the error code and differ the flow respect to the error code which is easier than comparing strings. you may inherit the exception with an errcode attribute. – ataman Sep 12 '15 at 22:23
  • @ataman, It would also be possible to adapt `std::system_error`, which includes error codes. – chris Sep 12 '15 at 22:28
  • @chris oh i was not aware of that. thanks;) – ataman Sep 12 '15 at 22:29
  • This isn't going to work out. Different APIs report errors differently. [CreateFile](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858.aspx) returns `INVALID_HANDLE_VALUE` on error, [CreateWindow](https://msdn.microsoft.com/en-us/library/windows/desktop/ms632679.aspx) returns `NULL`, and so on. – IInspectable Sep 12 '15 at 22:30
  • By the way, you might find more information in some of my old questions (disclaimer: I didn't start programming all too many years ago). I'm [not unfamiliar](http://stackoverflow.com/questions/9625526/check-at-compile-time-if-template-argument-is-void#comment13725278_9625526) with the approach you've chosen. – chris Sep 12 '15 at 22:42

2 Answers2

8

This function, as it stands, is of no use. Win32 functions do not, as a rule, indicate failure by setting the error code. They indicate failure via their return value. A BOOL that is false on failure. A handle that is NULL or INVALID_HANDLE_VALUE. And so on.

There are mis-behaving functions that fail, and indicate that in the return value, but do not set the error code. Your approach won't handle those correctly. There are functions that succeed, and set the error code. Again, your function will mis-treat them.

Each function has its own error handling rules. You have to treat each one on it own merits. Check the return value as described in that function's documentation.

Perhaps the best you can do is write a function that accepts a boolean indicating success, and throws an error on failure.

void Win32Check(bool success)
{
    if (!success)
        throw std::runtime_error(win_error_to_string(GetLastError()));
}

Call it like this:

// DeleteFile returns returns BOOL indicating success
Win32Check(DeleteFile(...));

Or

// CreateFile returns a sentinel to indicate failure
HANDLE hfile = CreateFile(...);
Win32Check(hfile != INVALID_HANDLE_VALUE);
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
1

You're getting there with the last part. If a function does not document that you can use GetLastError, then DO NOT use it. Unfortunately, you have to check each function's documentation separately to see this. For the purposes of this question, this means that the wrapper may only be used for functions that do specify the use of GetLastError, and you should check that the return value of the function represents failure before getting more error info.

To illustrate with an example, RegisterClass returns 0 on failure and advertises more error information through GetLastError. On the other hand, RegSetValueEx returns ERROR_SUCCESS (0) on success and does not advertise the same because it returns an error code directly. Meanwhile, there's WinExec, which returns a value greater than 31 on success and one of several listed error codes on failure. The latter are still free to call another function that happens to fail and call SetLastError, even though the call you make succeeds.

Another slight problem is that functions returning void will not work, but that can be fixed rather easily with a specialization, or a proposal going through for allowing void values that do nothing.


To further illustrate, here is part of the documentation for GetLastError:

The Return Value section of the documentation for each function that sets the last-error code notes the conditions under which the function sets the last-error code. Most functions that set the thread's last-error code set it when they fail. However, some functions also set the last-error code when they succeed. If the function is not documented to set the last-error code, the value returned by this function is simply the most recent last-error code to have been set; some functions set the last-error code to 0 on success and others do not.

chris
  • 60,560
  • 13
  • 143
  • 205
  • 1
    And don't forget functions that return `HRESULT` error codes instead of using `GetLastError()` (many/most of the Shell functions, etc). And also non-void returning functions that do not provide any error handling at all (some GDI functions, etc) – Remy Lebeau Sep 13 '15 at 03:41
  • @RemyLebeau, Yikes, COM completely slipped my mind, along with that other category. – chris Sep 13 '15 at 03:44
  • COM is not strictly part of the Windows API. However, COM does specify standard error reporting ([`SUCCEEDED`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms687197.aspx)/[`FAILED`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms693474.aspx) macros, and the [IErrorInfo Interface](https://msdn.microsoft.com/en-us/library/windows/desktop/ms221233.aspx)). COM lends itself a lot better to implementing a generalized error handling wrapper. – IInspectable Sep 13 '15 at 13:16
  • @IInspectable, Oh, for sure. – chris Sep 13 '15 at 13:48