Visual C++ in Short: Determining whether a path refers to a file system object

If you’ve been programming Windows for a while (prior to Windows 2000) you may well have come across a number of techniques to determine whether a path refers to a file system object such as a file or directory.

One approach is to check the result of the CreateFile function but this approach has a number of pitfalls. Checking whether a path refers to a file system object should not change the state of the file system object, which CreateFile does in most cases. You can combat this by playing around with the various flags. For example, if you only specify FILE_READ_ATTRIBUTES as the desired access then the file’s last access date won’t be updated. Of course you may not have the necessary authorization to open the file handle at all. There are also other headaches associated with this approach such as remembering to close the file handle should the call to CreateFile succeed. Determining whether the file handle refers to a file or directory involves additional function calls.

Another solution is to use the FindFirstFile function but it gets a lot more data than you might need and you also need to remember to close the search handle.

The shell provides the PathFileExists function which is simpler than the approaches mentioned thus far but is limited in that it does not distinguish between files and directories. Still, if that’s good enough then here’s all you need to do:

const BOOL exists = PathFileExists(L"<some path>");

To be able to distinguish between files and directories involves checking the attributes of the file system object. The simplest way to get a file’s attributes is with the GetFileAttributes function introduced with Windows 2000 a long time ago (see comments). Of course if it fails to return the file attributes you can call the GetLastError function to find out why. That in turn provides a great way to determine whether the path refers to a file system object at all. It can also tell you other useful information such as whether the path is even valid.

The beauty of this model is that you get to decide just how much information you need. For example if you only want to know whether the path exists you can simply check whether GetFileAttributes returns INVALID_FILE_ATTRIBUTES or not. Alternatively you can distinguish between different classes of failure.

The following example shows a simple helper function that singles out the ability for the system to find the specified path from other failures. If a file system object exists it returns S_OK. If the path is well formed but the system cannot find the path or file then it returns S_FALSE. In all other cases it simply returns the specific failure as an HRESULT. It even optionally tells you whether the file system object is a directory or not.

HRESULT FileExists(PCWSTR fileName, bool* isDirectory)
{
   ASSERT(0 != fileName);

   const DWORD attributes = ::GetFileAttributes(fileName);

   if (INVALID_FILE_ATTRIBUTES == attributes)
   {
       const DWORD error = ::GetLastError();

       if (ERROR_FILE_NOT_FOUND == error ||
           ERROR_PATH_NOT_FOUND == error)
       {
           return S_FALSE;
       }
       else
       {
           return HRESULT_FROM_WIN32(error);
       }
   }

   if (0 != isDirectory)
   {
       *isDirectory = 0 != (FILE_ATTRIBUTE_DIRECTORY & attributes);
   }

   return S_OK;
}

You could for example identify syntax errors using the ERROR_INVALID_NAME error code:

bool isDirectory = false;
const HRESULT result = FileExists(L"<some path>", &isDirectory);

if (S_OK == result)
{
    if (isDirectory)
    {
        wprintf(L"Its a directory");
    }
    else
    {
        wprintf(L"Its a file");
    }
}
else if (S_FALSE == result)
{
    wprintf(L"Not found");
}
else
{
    if (HRESULT_FROM_WIN32(ERROR_INVALID_NAME) == result)
    {
        wprintf(L"The syntax is incorrect");
    }
    else
    {
        // Something else is wrong...
        wprintf(AtlGetErrorDescription(result));
    }
}

Incidentally, the PathFileExists function I mentioned above uses GetFileAttributes internally if it determines that you’re running on a supported version of Windows. In other words it’s a reasonable fallback if you need to support older operating systems.

There are also various shortcuts depending on what you intend to do with the path. For example if you just want to create a file and make sure it doesn’t already exist you can simply specify the CREATE_NEW creation disposition and the CreateFile function will fail if the specified file already exists.

If you’re looking for one of my previous articles here is a complete list of them for you to browse through.

Produce the highest quality screenshots with the least amount of effort! Use Window Clippings.

5 Comments

  • ak: Granted the Windows SDK documentation requirements section is perhaps not the most accurate way of determining when a function was introduced, but that wasn’t really the point and I couldn’t be bothered to install Windows NT 4 in a VM just to confirm.

    The point I was trying to make is that GetFileAttributes has been around for a long time (I consider the release of Windows 2000 a long time ago) and you shouldn’t have to resort to other more complex techniques to determine whether a file/directory exists. I do however know developers who still need to support Windows NT 4 which is unfortunate.

    Code in the .NET Framework and shell code like PathFileExists still check whether GetFileAttributes(Ex) is available based on OS version since that code was originally written to support operating systems that did not in fact support it.

  • ...and one more thing:
    "Code in the .NET Framework and shell code like PathFileExists still check whether GetFileAttributes(Ex) is available based on OS version since that code was originally written to support operating systems that did not in fact support it" makes no sense to me, since GetFileAttributes is supported on every OS where PathFileExists and/or .NET could exist

  • ak: No problem. :)

    The .NET code is really old thus the heavy legacy implementation. You can also take a look inside PathFileExists with Visual C++ if you load the OS symbols and you’ll see similar logic (except it calls the more appropriate GetFileAttributes), although I don’t remember whether VC6 uses the same symbol format.

  • Dioksin: Not too sure off the top of my head. You may want to look into use records. For example, how is your access to the UNC path being authenticated... is a session being established... You can check with “net use” on the command like or the NetUseXxx functions. Hope that helps.

  • Authentication is successful. I can access to this network folder from, say, File Explorer. After that the call of your function is successful too. But if I make log off from Windows and back log on with the same credentials in this case only FIRST call of ::GetFileAttributes is wrong.

Comments have been disabled for this content.