I purposely set a bad path for a CreateDirectory
call so that my exception handling code would execute:
I am not sure if this is off topic, but you might have more experience with this. Why is the error text:
An attempt was made to reference a token that does not exist.
Why are they using the word token instead of file or folder?
I will close the question if off topic.
The return value of GetLastError
is: 123
According to here:
ERROR_INVALID_NAME
123 (0x7B)
The filename, directory name, or volume label syntax is incorrect.
Now that message makes sense. So why is my Windows 10 showing the other message?
There is no issue with the call to FormatMessage
. It works as advertised. However, you aren't passing in the value 123 (ERROR_INVALID_NAME
). You are passing 1008 (ERROR_NO_TOKEN
), by accident, due to calling GetLastError
at the wrong time. GetLastError has a strong requirement:
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 callSetLastError
with a zero when they succeed, wiping out the error code set by the most recently failed function.
It's fairly straightforward to satisfy this in C. With C++, things get more complicated, with all the invisible code the compiler generates. The code in question apparently captures the calling thread's last error code only after it enters the CWin32FileError
c'tor. That's too late.
Based on the assumption that GetWorkingPath()
returns a CString
instance by value, and CWin32FileError
takes its arguments as CString const&
, this is what happens behind the scenes:
if (!CreateDirectory(GetWorkingPath() + _T("whatever"), nullptr))
GetWorkingPath()
constructs a temporary CString
instance.operator+(CString const&, LPCTSTR)
constructs yet another temporary CString
instance, concatenating both inputs.operator LPCTSTR()
is implicitly invoked on the temporary constructed in step 2.CreateDirectory
is called and returns.Steps 5 and 6 are fatal already, potentially changing the calling thread's last error code. And yet, there's even more code getting in the way:
CWin32FileError e(_T("whatever"),
GetWorkingPath() + _T("whatever"));
_T("whatever")
triggers CString
's conversion constructor (CString(LPCTSTR)
), producing a temporary.GetWorkingPath()
constructs a temporary, invoking CString
's copy-c'tor.operator+(CString const&, LPCTSTR)
constructs yet another temporary.CWin32FileError
c'tor finally runs, presumably calling GetLastError
.This adds another 3 candidates (at least) that can modify the calling thread's last error code. To solve this, you're going to have to make sure, that absolutely no code runs in between a failed Windows API call and the call to GetLastError
.
To do this, you're going to have to get rid of the temporaries, and move capturing of the last error code outside the CWin32FileError
c'tor. A simple solution to the former would be to construct the path name up front, e.g.
auto path_name{ GetWorkingPath() + _T("whatever") };
auto path_name_strptr{ path_name.GetString() };
if (!CreateDirectory(path_name_strptr, nullptr))
// ...
(or use an init-statement in the if statement to limit the scope, if you are using C++17). Either way, your very next call must be GetLastError
to capture the last error code while it is still meaningful. However you pass that value into CWin32FileError
's c'tor, or which argument types it uses, is up to you. But you cannot rely on that c'tor to capture the last error code for you.