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.