draganm17
draganm17

Reputation: 89

about WINAPI errors handling

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?

Upvotes: 6

Views: 3460

Answers (2)

David Heffernan
David Heffernan

Reputation: 613572

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);

Upvotes: 8

Qaz
Qaz

Reputation: 61970

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.

Upvotes: 1

Related Questions