Search code examples
winapitokengetlasterror

Why Microsoft Error Message uses the word "Token"?


I purposely set a bad path for a CreateDirectory call so that my exception handling code would execute:

Microsoft Error Text

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?


Solution

  • 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 call SetLastError 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))
    
    1. GetWorkingPath() constructs a temporary CString instance.
    2. operator+(CString const&, LPCTSTR) constructs yet another temporary CString instance, concatenating both inputs.
    3. operator LPCTSTR() is implicitly invoked on the temporary constructed in step 2.
    4. CreateDirectory is called and returns.
    5. Important: The destructor of the temporary created in step 2 is called.
    6. Important: The destructor of the temporary created in step 1 is called.

    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"));
    
    1. Important: _T("whatever") triggers CString's conversion constructor (CString(LPCTSTR)), producing a temporary.
    2. Important: GetWorkingPath() constructs a temporary, invoking CString's copy-c'tor.
    3. Important: operator+(CString const&, LPCTSTR) constructs yet another temporary.
    4. The 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.