如何在C语言中检查Windows上的目录是否存在?

76

问题

在Windows C应用程序中,我想验证传递给函数的参数以确保指定的路径存在。

如何在C语言中检查Windows上的目录是否存在?

* 我知道你可能会遇到竞态条件,在你检查路径是否存在和使用路径之间的时间内,路径可能已经不存在了,但我可以解决这个问题。

附加背景

当涉及到权限时,明确地知道一个目录是否存在可能会变得棘手。尝试确定目录是否存在时,进程可能无权访问目录或父目录。对于我的需求来说是可以接受的。如果目录不存在或者我无法访问它,两种情况都被视为应用程序中的无效路径错误,所以我不需要区分它们。如果您的解决方案提供了这种区别,那就更好了。

任何使用C语言、C运行时库或Win32 API的解决方案都可以,但最好使用通常加载的库(例如kernel32、user32等),并避免涉及加载非标准库的解决方案(例如Shlwapi.dll中的PathFileExists)。同样,如果您的解决方案是跨平台的,则获得(虚拟)奖励分。

相关

如何使用Win32程序检查文件是否存在?


1
你所说的“我无法访问它”是指什么?读取访问权限?写入访问权限?删除文件的访问权限? - Jim Mischel
好问题。针对这个目的,读取权限。我会假设(读:可能有点愚蠢)只检查读取权限就足够了,因为在那个目录中尝试执行任何文件访问(读写删)将导致相应的API调用失败(例如CreateFile,WriteFile)。然而,如果你甚至无法读取目录(要么是因为目录不存在,要么是因为你没有权限),那么在调用文件访问函数时会出现与路径问题无法区分的失败情况。 - Zach Burlingame
在Windows API中,我认为您也可以使用FindFirstFile()来测试文件是否存在。http://msdn.microsoft.com/en-us/library/windows/desktop/aa364418(v=vs.85).aspx - Indinfer
“通常加载”的库和“非标准”的库并不像最后一段所暗示的那样是互斥的。 - Adrian McCarthy
可能是重复的问题:如何使用Win32程序检查文件是否存在? - Adrian McCarthy
不是重复的,这是关于目录的,那个是关于文件的。 - Zach Burlingame
5个回答

103

可以尝试像这样做:

BOOL DirectoryExists(LPCTSTR szPath)
{
  DWORD dwAttrib = GetFileAttributes(szPath);

  return (dwAttrib != INVALID_FILE_ATTRIBUTES && 
         (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}

GetFileAttributes()方法包含在Kernel32.dll中。


5
如果szPath是"C:\",GetFileAttributes、PathIsDirectory和PathFileExists函数将不能正常工作。 - zwcloud
我刚刚发现GetFileAttributes()无法看到一些虚拟COM端口,而这些端口对CreateFile()是完全可用的。尽管头文件中存在FILE_ATTRIBUTE_DEVICE。唉。 - frr
@zwcloud 你在哪里测试的?GetFileAttributes 对我来说运行正常。 - Martin

26

这是一个完全与平台无关的解决方案(使用标准C库)

编辑:要在Linux中编译此代码,请用<unistd.h>替换<io.h>,并将_access替换为access。对于真正的跨平台解决方案,请使用Boost FileSystem库。

#include <io.h>     // For access().
#include <sys/types.h>  // For stat().
#include <sys/stat.h>   // For stat().

bool DirectoryExists( const char* absolutePath ){

    if( _access( absolutePath, 0 ) == 0 ){

        struct stat status;
        stat( absolutePath, &status );

        return (status.st_mode & S_IFDIR) != 0;
    }
    return false;
}

一个支持MBCS和UNICODE构建的特定于Windows的实现:

#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <tchar.h>

BOOL directory_exists( LPCTSTR absolutePath )
{
  if( _taccess_s( absolutePath, 0 ) == 0 )
  {
    struct _stat status;
    _tstat( absolutePath, &status );
    return (status.st_mode & S_IFDIR) != 0;
  }

  return FALSE;
}

6
这并不是跨平台或标准化的,而是微软对 POSIX 的模仿(实际上你会从 <unistd.h> 获取 access 而且不会以下划线开头)。 - asveikau
哎呀,我的错。你是对的,如果我寻找 #included .h 的话,它们在 Visual Studio 文件夹内。还有一件事:当你说“模仿”时,你的意思是它实际上不符合 POSIX 标准吗?也就是说...这在 Linux 上无法编译? - dario_ramos
2
对,这不是 POSIX,其他平台上没有 io.h 或带下划线的 _access - asveikau
1
我在Windows上使用这个解决方案时遇到了正斜杠的问题,尽管每个人都认为Win32接受/而不是\。我对此感到困惑,但似乎在Win32上要小心处理POSIX路径和POSIX libc函数。 - mxcl
是的,就像asveikau所说,Windows对POSIX的实现并不完美。如果你想要可移植的文件系统访问,请使用Boost(这就是我现在正在做的)。 - dario_ramos
在Windows上,_stat及其相关函数无法处理C:\路径。 - Violet Giraffe

10
如果使用Shell轻量级API(shlwapi.dll)对您来说是可以的,那么您可以使用PathIsDirectory函数

5
另一种选择是shell函数PathFileExists()。 PathFileExists()文档 该函数“确定文件系统对象(如文件或目录)的路径是否有效。”

2

所以,这个问题充满了边缘情况。一个真正的答案应该是这样的:

BOOL DirectoryExists(LPCTSTR szPath, BOOL *exists)
{
  *exists = FALSE;
  size_t szLen = _tcslen(szPath);
  if (szLen > 0 && szPath[szLen - 1] == '\\') --szLen;
  HANDLE heap = GetProcessHeap();
  LPCTSTR szPath2 = HeapAlloc(heap, 0, (szlen + 3) * sizeof(TCHAR));
  if (!szPath2) return FALSE;
  CopyMemory(szPath2, szPath, szLen * sizeof(TCHAR));
  szPath2[szLen] = '\\';
  szPath2[szLen + 1] = '.';
  szPath2[szLen + 2] = 0;
  DWORD dwAttrib = GetFileAttributes(szPath2);
  HeapFree(heap, 0, szPath2);

  if (dwAttrib != INVALID_FILE_ATTRIBUTES) {
    *exists = TRUE; /* no point checking FILE_ATTRIBUTE_DIRECTORY on "." */
    return TRUE;
  }
  /*
   * If we get anything other than ERROR_PATH_NOT_FOUND then something's wrong.
   * Could be hardware IO, lack of permissions, a symbolic link pointing to somewhere
   * you don't have access, etc.
   */
  return GetLastError() != ERROR_PATH_NOT_FOUND;
}

DirectoryExists的正确结果是三态。有三种情况需要处理。它存在、不存在或者你无法检查。在生产中,我因为SAN返回NO到检查函数,然后让我在代码的下一行创建了一个覆盖它的东西而丢失了数据。不要犯微软在C#中设计File.Exists() API时犯的同样错误。
然而,我看到很多检查是多余的。不要写像if (directory doesn't exist) CreateDirectory();这样的东西;只需写CreateDirectory() if (GetLastError() != 0 && GetLastError() != ERROR_ALREADY_EXISTS。或者相反地使用RemoveDirectory();只需删除它并检查是否没有错误或路径未找到。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接