Why is there a RestoreLastError function that does the same thing as SetLastError?


Matt Pietrek noticed that Set­Last­Error and Restore­Last­Error do exactly the same thing and wondered why there's a separate function for it.

It's to assist in debugging and diagnostics.

Say you're debugging a problem and when you call Get­Last­Error you get ERROR_ACCESS_DENIED. It would really help a lot if you could figure out who set the error code to ERROR_ACCESS_DENIED. If you set a breakpoint on Set­Last­Error, you find that people call Set­Last­Error for two different reasons:

  1. To report an error.
  2. To restore the error code to what it was before they did something that might change the last error code.

That second one needs a little explanation. You might have a logging function that goes like this:

// Remember, code in italics is wrong
void LogSomething(blah blah)
{
 DWORD dwError = GetLastError();
 ... do logging stuff ...
 SetLastError(dwError);
}

// or if you prefer RAII

class PreserveLastError
{
public:
    PreserveLastError() : m_dwLastError(GetLastError()) {} 
    ~PreserveLastError() { SetLastError(m_dwLastError); }
private:
    DWORD m_dwLastError;
};

void LogSomething(blah blah)
{
 PreserveLastError preserve;

 ... do logging stuff ...
}

It's important that functions which perform logging, assertion checking, and other diagnostic operations are nonintrusive. You don't want a bug to go away when you turn on logging because the logging code somehow perturbed the system. Therefore, your logging function saves the value of Get­Last­Error() and sets that back as the error code when it's done, so that any errors that took place during logging do not escape and inadvertently affect the rest of the program.

Now let's go back to the code that's trying to figure out who set the error code to ERROR_ACCESS_DENIED. You set up your debugging diagnostic tool and tell it to record everybody who calls Set­Last­Error() and pay particular attention to everybody who sets the error to ERROR_ACCESS_DENIED. You then run your scenario, your program encounters the failure you're trying to debug, and you ask the diagnostic tool, "Tell me who set the error code to ERROR_ACCESS_DENIED." The diagnostic tool says, "Ah, I have that in my history. The function that set the error code to ERROR_ACCESS_DENIED is... Log­Something!"

Of course, Log­Something wasn't really the originator of the ERROR_ACCESS_DENIED; it was just restoring things to how it found them. The real ERROR_ACCESS_DENIED came from somebody else, and the log function was just being careful not to disturb it.

...
  if (!FunctionX()) {
    LogSomething("Function X failed");
  } else {
    LogSomething("Function X succeeded");
   FunctionY(); // also does some logging
  }
  FunctionZ(); // also does some logging
  Assert(EverythingOkay()); // assertion fires
  // GetLastError() returns ERROR_ACCESS_DENIED
...

All those calls to logging functions in between called Get­Last­Error() and got ERROR_ACCESS_DENIED back, then when the logging was complete, they called Set­Last­Error(ERROR_ACCESS_DENIED) to put things back. Your diagnostic error-tracing tool gleefully points the finger at your logging function: "Look! Look! This guy set the error code to ERROR_ACCESS_DENIED!"

Enter Restore­Last­Error. This function does the same thing as Set­Last­Error, but its use is a message to diagnostic tools that "Sure, you may see me set an error code, but it wasn't my idea. I'm just trying to put things back the way I found them. Keep looking backwards in your history."

(The message also works forward in time: If you want to catch ERROR_ACCESS_DENIED in the act, you might set a breakpoint on Set­Last­Error, and then get frustrated that the breakpoint keeps getting hit by your logging function. Switching the logging function to Restore­Last­Error keeps the breakpoint on Set­Last­Error from firing spuriously.)

The corrected version of the Log­Something function is therefore something like this:

void LogSomething(blah blah)
{
 DWORD dwError = GetLastError();
 ... do logging stuff ...
 RestoreLastError(dwError);
}

// or if you prefer RAII

class PreserveLastError
{
public:
    PreserveLastError() : m_dwLastError(GetLastError()) {} 
    ~PreserveLastError() { RestoreLastError(m_dwLastError); }
private:
    DWORD m_dwLastError;
};
Comments (18)
  1. Henke37 says:

    I just hope that the debugger is smart enough to realize that if these two functions are separate even if they both points at the same address to save space. But that may not be the case.

  2. WndSks says:

    I did not know about this API, then again msdn.microsoft.com/…/ms679321%28v=VS.85%29.aspx and msdn.microsoft.com/…/ms680347%28v=VS.85%29.aspx don't know about it either. Did it exist in XP RTM or was it added in a SP? Either way it is safe to use in x64 builds….

  3. Stephen Cleary says:

    @WndSks: Same here. So, if it's not documented, aren't we supposed to ignore it?

  4. Dan Bugglin says:

    That class may not be a good idea; as you yourself have said Raymond, who knows when that destructor would run?

  5. Dave says:

    @MAZZTer, C++ destructors have deterministic behavior. They run when the item drops out of scope.

    @WndSks, Matt Pietrek's article says it was introduced in Windows Server 2003, so it's not in XP.

    If more people understand the way *LastError() works, maybe we can get rid of those silly "An Error Occurred: The operation completed successfully" dialogs that some apps love to display. Like this one: blogs.msdn.com/…/frustrating-error-using-remote-desktop.aspx

  6. WndSks says:

    @Dave: XP.SP1 does export that function and wikipedia says it was released in 2002 and Win2003.RTM was released in 2003. A couple quotes from the article: "Actually, the majority of the new APIs (relative to Windows 2000) first appeared in Windows XP, rather than in Windows Server 2003" "I've compared the exports from the Windows Server 2003 version of NTDLL.DLL to the Windows 2000 version. As you might expect, lots of APIs were added and a few have disappeared" And since the function is not documented as far as I can tell, the only thing we have to go on is whether kernel32 exports it or not…

    (OT: The submit form eats my comment the first time so I have to repost, whats up with that?)

  7. Hans says:

    Isn't it always been that if you have an API function, which signals failure

    by their special means (returning FALSE, NULL handle, INVALID_HANDLE_VALUE or whatever),

    you only can get it's last error by GetLastError() before you call any other API function.

    The caller of GetLastError() can not assume if he calls a log function, he still

    get the error code of the API function which signaled failure before.

    So I don't think RestoreLastError() helps for debugging, and I didn't know until reading

    your blog that this function exists.

  8. Ken Hagan says:

    wndsks and Stephen Cleary: I can't find RestoreLastError either, but there's SetLastErrorEx, which looks even more pointless because it does the same thing AND has an ignored second argument. Umm, Raymond, can you shed light?

  9. Hans says:

    Ken, with API I meant windows API (aka system function) of course.

    How a function of the the windows API itself uses other windows API functions to get the task done is up to itself, but also not interesting for me.

  10. SimonRev says:

    @MAZZTer — the fact that in C++ you know when your destructors run (and the RAII implications that it carries) is most important thing that makes C++ preferable IMO to any managed language.  This is in spite of all the bells and whistles that C# adds.

    What Raymond did is perfectly fine in C++.

  11. Matt Pietrek says:

    Thanks for the background Raymond! I had completely forgotten about point this out till you brought it up. :-)

  12. Adam Rosenfield says:

    Of course, setting a breakpoint in SetLastError will give you bajillions of false positives of various functions doing SetLastError(0) upon entry.  You'd probably want to make it a conditional breakpoint with the condition of dwErrCode!=0 (which you may need to write as something like *(int*)(ESP+8)!=0 or RCX!=0, depending on architecture, if you don't have symbols) to be somewhat useful.

  13. WndSks says:

    @Ken Hagan: IIRC some components call SetLastErrorEx with a non 0 type, see RIP_INFO ( msdn.microsoft.com/…/ms680587%28v=vs.85%29.aspx ) for a list of SLE_* types…

  14. Ken Hagan says:

    But Hans, what about the caller's caller?

    You may be calling an API and that API itself asks some other system to do something. The latter call fails. The first API wants to be able to log the failure but it has to be able to return to the original caller (you) in such a state that you can call GetLastError() and get the right answer, because as far as *you* are concerned, you *are* calling GetLastError() immediately after receiving the error code.

  15. rs says:

    @Hans: I think functions (especially cleanup functions) should usually not call SetLastError on success. For example, a successful CloseHandle(hfile) after a failed WriteFile(hfile, …) should not unset the error code. However, some functions (such as GlobalUnlock used with the clipboard) do not follow this rule, and in those cases RestoreLastError can be useful.

  16. Ken Hagan says:

    "How a function of the the windows API itself uses other windows API functions to get the task done is up to itself, but also not interesting for me."

    …which is precisely why that function may need to call RestoreLastError. You'd only need it yourself if you wanted to present a similar behaviour to your own clients, which your clients might think is a reasonable thing to do.

  17. Pavel Lebedinsky says:

    The easiest way to catch a specific last error value in debugger is to set ntdll!g_dwLastErrorToBreakOn to that value.

  18. Adam Rosenfield says:

    @rs: You may be interested to know that OpenGL follows that model.  If an error occurs inside some OpenGL function, the error is sticky — that error code does not change until you call glGetError() to retrieve it.  This saves you the trouble of having to call glGetError() after every API call (of which there are many), so you can perform a big batch of rendering operations and then check to see if anything had an error.

    This works well for a rendering API where function calls are extremely unlikely to fail (and when they do, it's usually something like an invalid parameter, doing something between glBegin() and glEnd(), or unsupported hardware), but I don't think that model works as well for an API such as Win32 because if you don't check forget to call GetLastError() after something inconsequential fails, you've suddenly lost the ability to get error codes for other things that fail.

Comments are closed.