I'm calling Windows APIs from C++ code and I have a helper method to do the FormatMessage
stuff and throw an exception for error handling. The signature of the function is
void throw_system_error(const std::string& msg_prefix, DWORD messageid)
I've noticed something strange. This code does not work properly:
handle = ...;
if (handle == NULL) {
throw_system_error("something bad happened", GetLastError());
}
The error code that is passed to throw_system_error
is always zero.
Where as this works just fine:
handle = ...;
if (handle == NULL) {
DWORD error = GetLastError();
throw_system_error("something bad happened", error);
}
Some more investigation showed that this version has the same problem:
handle = ...;
if (handle == NULL) {
std::string msg("something bad happened");
DWORD error = GetLastError();
throw_system_error(msg, error);
}
It looks for all the world as if the constructor of std::string
is resetting the error code.
My guess would be that std::string
is allocating memory internally which causes some system call that then sets the last error back to zero.
Anyone knows what is actually going on here?
Visual C++ 2015, 64bit.
Let's see the GetLastError documentation:
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.
You should call the GetLastError function immediately when a
function's return value indicates that such a call will return useful
data. That is because some functions call SetLastError with a zero
when they succeed, wiping out the error code set by the most recently
failed function.
So there is one function calling SetLastError, very likely one that allocates memory:
When you construct a string, new
is called to allocate memory.
Now, let's see new
's implementation in vc++. There is a very good answer to that question in Stack Overflow : https://softwareengineering.stackexchange.com/a/293209
It depends if you are in debug or release mode. In release mode, there is HeapAlloc/HeapFree which are kernel functions,
while in debug mode (with visual studio) there is a hand written
version of free and malloc (to which new/delete are re-directed) with
thread locks and more exceptions detection, so that you can detect
more easily when you did some mistakes with you heap pointers when
running your code in debug mode.
So in release mode, the function called is HeapAlloc, which does NOT call SetLastError. From the documentation:
If the function fails, it does not call SetLastError
So the code should work properly in release mode.
However, in the debug implementation, the function FlsGetValue is called, and that function calls SetLastError when succeeded.
It's very easy to check this,
#include <iostream>
#include <Windows.h>
int main() {
DWORD t = FlsAlloc(nullptr);
SetLastError(23); //Set error to 23
DWORD error1 = GetLastError(); //store error
FlsGetValue(t); //If success, it is going to set error to 0
DWORD error2 = GetLastError(); //store second error code
std::cout << error1 << std::endl;
std::cout << error2 << std::endl;
system("PAUSE");
return 0;
}
It outputs the following:
23
0
So FlsGetValue has called SetLastError(). To prove that it is called only on debug we can do the following test:
#include <iostream>
#include <Windows.h>
int main() {
DWORD t = FlsAlloc(nullptr);
SetLastError(23); //Set error to 23
DWORD error1 = GetLastError(); //store error
int* test = new int; //allocate int
DWORD error2 = GetLastError(); //store second error code
std::cout << error1 << std::endl; //output errors
std::cout << error2 << std::endl;
delete test; //free allocated memory
system("PAUSE");
return 0;
}
If you run it in debug mode, it will give you, because it calls FlsGetValue:
23
0
However, if you run it in release mode, it produces, because it calls HeapAlloc:
23
23
Per 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.
At some point during the construction of std::string
, SetLastError
is called. The standard library on windows uses Win32 calls as part of its implementation.
Your second method (that works) is the correct way to use GetLastError
You should call the GetLastError function immediately when a function's return value indicates that such a call will return useful data. That is because some functions call SetLastError with a zero when they succeed, wiping out the error code set by the most recently failed function.
This is normal - the "last error" can be set indirectly through any function call.
Some functions set it to "no error" on success, so if you want to use it reliably you need to store it immediately before you do anything else.
If you've ever encountered an "A serious error occurred: The operation completed successfully" dialogue, this is probably the reason.