为什么我在递归删除目录时遇到问题?

4

我编写了一个应用程序,使用WIN32 API创建临时目录层次结构。现在,在关闭应用程序时想要删除目录时,我遇到了一些问题。

假设我有一个目录层次结构:C:\temp\directory\subdirectory\

我正在使用这个递归函数:

bool Dir::deleteDirectory(std::string& directoryname, int flags)
{
    if(directoryname.at(directoryname.size()-1) !=  '\\') directoryname += '\\';

    if ((flags & CONTENTS) == CONTENTS)
    {
        WIN32_FIND_DATAA fdata;
        HANDLE dhandle;

        directoryname += "\\*";
        dhandle = FindFirstFileA(directoryname.c_str(), &fdata);

        // Loop through all the files in the main directory and delete files & make a list of directories
        while(true)
        {
            if(FindNextFileA(dhandle, &fdata))
            {
                std::string filename = fdata.cFileName;
                if(filename.compare("..") != 0)
                {
                    std::string filelocation = directoryname.substr(0, directoryname.size()-2) + StringManip::reverseSlashes(filename);

                    // If we've encountered a directory then recall this function for that specific folder.
                    if(!isDirectory(filelocation))  DeleteFileA(filename.c_str());
                    else deleteDirectory(filelocation, DIRECTORY_AND_CONTENTS);
                }
            } else if(GetLastError() == ERROR_NO_MORE_FILES)    break;
        }
        directoryname = directoryname.substr(0, directoryname.size()-2);
    }

    if ((flags & DIRECTORY) == DIRECTORY)
    {
        HANDLE DirectoryHandle;
        DirectoryHandle = CreateFileA(directoryname.c_str(),
                                FILE_LIST_DIRECTORY,
                                FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                NULL,
                                OPEN_EXISTING,
                                FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                                NULL);
        bool DeletionResult = (RemoveDirectoryA(directoryname.c_str()) != 0)?true:false;
        CloseHandle(DirectoryHandle);
        return DeletionResult;
    }

     return true;
}

这个函数遍历临时目录的内容,并对临时目录中的每个目录进行递归调用,直到它到达最低的目录; 在示例中为子目录。

还定义了三个标志。

 enum DirectoryDeletion
 {
    CONTENTS = 0x1,
    DIRECTORY = 0x2,
    DIRECTORY_AND_CONTENTS = (0x1 | 0x2)
 };

当使用此函数时,它只会删除最低级的子目录,而我无法删除更高层次的目录,因为它会提示该目录不为空。当我查看目录时,'subdirectory'只有在应用程序结束后才被删除。然而,当我尝试将其封装在一个非递归的简单主应用程序中时,删除目录时就没有任何问题。

当我看到你的主题行时,我以为你试图删除win32目录... - rmeador
7个回答

13

有一个Windows API叫做SHFileOperation,它可以为您执行递归文件夹删除。

LONG DeleteDirectoryAndAllSubfolders(LPCWSTR wzDirectory)
{
    WCHAR szDir[MAX_PATH+1];  // +1 for the double null terminate
    SHFILEOPSTRUCTW fos = {0};

    StringCchCopy(szDir, MAX_PATH, wzDirectory);
    int len = lstrlenW(szDir);
    szDir[len+1] = 0; // double null terminate for SHFileOperation

    // delete the folder and everything inside
    fos.wFunc = FO_DELETE;
    fos.pFrom = szDir;
    fos.fFlags = FOF_NO_UI;
    return SHFileOperation( &fos );
}

2
警告:此功能在Vista+上无法使用。是的,MSDN文档说它只被IFileOperation“取代”,但实际上,在Vista和7上,FO_DELETE已经损坏了。 - Mahmoud Al-Qudsi
2
抱歉,但我不同意。我刚刚在Win7上编译并调用了上面的代码: DeleteDirectAndAllSubfolders(L"D:\somefolder") 运行得很好。现在我回想起当我第一次研究这个问题时,它可能在XP上无法正常工作。但是我现在没有看到任何文档表明这一点。如果目录正在使用中(例如具有打开的shell文件夹和/或dos提示符,并且curdir在该文件夹中),它将失败。 - selbie

8

你没有在所有FindFirstFile调用中关闭dhandle,因此当你尝试删除目录时,每个目录都有一个对它的引用。

另外,为什么需要创建DirectoryHandle? 它是不需要的,并且可能会阻止目录删除。

当应用程序关闭时,这些句柄被强制关闭,最后一次尝试删除然后成功(我猜测)。


确实,忘记FindClose。谢谢! - Charles

7

SHFileOperations在Windows 7上表现很好。实际上,在IFileOperation文档中指出:

IFileOperation只能应用于单线程公寓(STA)情况。它不能用于多线程公寓(MTA)情况。对于MTA,仍必须使用SHFileOperation。

然而,我对SHFileOperations的问题是它似乎不支持超过260个字符的路径,并且不支持长文件名的\?\前缀。

这真的很痛苦...但如果您想处理超过260个字符的路径(NTFS支持但Windows Explorer、命令提示符命令等不支持),仍然需要递归函数。


6

嗯,我发现这段代码中有几个漏洞...以下是我的发现:


bool Dir::deleteDirectory(std::string& directoryname, int flags)
{
 if(directoryname.at(directoryname.size()-1) !=  '\\') directoryname += '\\';

 if ((flags & CONTENTS) == CONTENTS)
 {
  WIN32_FIND_DATAA fdata;
  HANDLE dhandle;
  //BUG 1: Adding a extra \ to the directory name..
  directoryname += "*";
  dhandle = FindFirstFileA(directoryname.c_str(), &fdata);
  //BUG 2: Not checking for invalid file handle return from FindFirstFileA
  if( dhandle != INVALID_HANDLE_VALUE )
  {
      // Loop through all the files in the main directory and delete files & make a list of directories
   while(true)
   {
    if(FindNextFileA(dhandle, &fdata))
    {
     std::string     filename = fdata.cFileName;
     if(filename.compare("..") != 0)
     {
      //BUG 3: caused by BUG 1 - Removing too many characters from string.. removing 1 instead of 2
      std::string filelocation = directoryname.substr(0, directoryname.size()-1) + filename;

      // If we've encountered a directory then recall this function for that specific folder.

      //BUG 4: not really a bug, but spurious function call - we know its a directory from FindData already, use it.
      if( (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)  
       DeleteFileA(filelocation.c_str());
      else 
       deleteDirectory(filelocation, DIRECTORY_AND_CONTENTS);
     }
    } else if(GetLastError() == ERROR_NO_MORE_FILES)    break;
   }
   directoryname = directoryname.substr(0, directoryname.size()-2);
   //BUG 5: Not closing the FileFind with FindClose - OS keeps handles to directory open.  MAIN BUG
   FindClose( dhandle );
  }
 }
 if ((flags & DIRECTORY) == DIRECTORY)
 {
  HANDLE DirectoryHandle;
  DirectoryHandle = CreateFileA(directoryname.c_str(),
   FILE_LIST_DIRECTORY,
   FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
   NULL,
   OPEN_EXISTING,
   FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
   NULL);
  //BUG 6: Not checking CreateFileA for invalid handle return.
  if( DirectoryHandle != INVALID_HANDLE_VALUE )
  {

   bool DeletionResult = (RemoveDirectoryA(directoryname.c_str()) != 0)?true:false;
   CloseHandle(DirectoryHandle);
   return DeletionResult;
  }
  else
  {
   return true;
  }
 }

 return true;
}

1

尝试调用FindClose来关闭FindFileFileA返回的句柄。


1

我没有看到你的 dhandleFindClose。一个打开的句柄意味着该目录仍在使用中。

MSDN 表示:"当不再需要搜索句柄时,请使用 FindClose 函数关闭它,而不是使用 CloseHandle。"

CloseHandle 在下面进入的 DirectoryHandle 中似乎是正确的,但对于 Find 循环中使用的 dhandle 不是。)


0
主要问题已经被回答,但这是我注意到的。你的主要while循环对我来说似乎有点脆弱...
while(true)
{
     if(FindNextFileA(dhandle, &fdata))
     {
         //...
     } else if(GetLastError() == ERROR_NO_MORE_FILES)    break;
}

如果FindNextFile因目录中没有更多文件而结束,则操作将停止。但是如果由于其他原因导致它结束呢?如果发生异常情况,似乎会导致无限循环。

我认为,如果FindNextFile因为任何原因失败,那么您将希望停止循环并开始通过递归调用返回。所以我建议简单地删除GetLastError测试并只制作“else break;


实际上,经过一番思考,我可能会将其简化为:

while(FindNextFileA(dhandle, &fdata))
{
    //...
}

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