【问题标题】:Why am I having problems recursively deleting directories?为什么我在递归删除目录时遇到问题?
【发布时间】:2010-11-30 22:12:35
【问题描述】:

我编写了一个使用 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;
}

这个函数遍历临时目录的目录内容;并且对于临时目录中的每个目录,它都会不断地回忆自己,直到它位于最低目录;示例中的子目录。

还定义了 3 个标志

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

使用此功能时,它只删除最低的子目录,我无法删除层次较高的子目录,因为它说目录不为空。当我去查看目录时,“子目录”仅在应用程序结束后被删除。但是,当我尝试将其封装在一个非递归的简单主应用程序中时,删除目录完全没有问题。

【问题讨论】:

  • 当我阅读您的主题行时,我以为您正在尝试删除 win32 目录...

标签: c++ winapi recursion directory


【解决方案1】:

有一个 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 );
}

【讨论】:

  • 警告:这在 Vista+ 上不起作用。是的,MSDN 文档说它只是被 IFileOperation “取代”,但实际上,FO_DELETE 在 Vista 和 7 上已损坏。
  • 对不起,我不同意。我刚刚在 Win7 上通过编译并调用了上面的代码: DeleteDirectAndAllSubfolders(L"D:\\somefolder") 效果很好。现在我记得当我第一次研究这个时,它可能不适用于 XP。但是我现在在文档中看不到任何建议。如果该目录正在使用中(例如在该文件夹中有一个打开的 shell 文件夹和/或带有 curdir 的 dos 提示符),它将失败
【解决方案2】:

您并没有从所有这些 FindFirstFile 调用中关闭 dhandle,因此当您尝试删除它时,每个目录都会引用它。

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

当您的应用关闭时,这些句柄将被强制关闭,并且(我猜)最后一次尝试删除然后成功。

【讨论】:

  • 确实,忘记 FindClose。谢谢!
【解决方案3】:

SHFileOperations 在 Windows 7 上运行良好。事实上在 IFileOperation 文档中说

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

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

这是一个真正的痛苦......但如果您想要处理超过 260 个字符的路径(NTFS 支持 - 但不支持 Windows 资源管理器、命令提示符命令等),仍然需要递归函数

【讨论】:

    【解决方案4】:

    好吧,我在这段代码中发现了几个错误。这就是我发现的

    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;
    }
    

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        我没有看到您的dhandleFindClose。打开的句柄表示该目录仍在使用中。

        MSDN 说:“当不再需要搜索句柄时,使用 FindClose 函数关闭它,而不是 CloseHandle。”

        CloseHandle 似乎更适合您的DirectoryHandle,但不适用于查找循环中使用的dhandle。)

        【讨论】:

          【解决方案7】:

          主要问题已经得到解答,但我注意到了一些问题。你的主要while 循环对我来说似乎有点脆弱......

          while(true)
          {
               if(FindNextFileA(dhandle, &fdata))
               {
                   //...
               } else if(GetLastError() == ERROR_NO_MORE_FILES)    break;
          }
          

          如果FindNextFile 结束,因为目录中没有更多文件,这将结束。但是,如果它因其他原因而结束呢?如果发生异常情况,您可能会陷入无限循环。

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


          实际上,经过片刻的思考,我可能会将其简化为:

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

          【讨论】:

            猜你喜欢
            • 2023-01-02
            • 2013-06-07
            • 2014-05-12
            • 2020-04-05
            • 1970-01-01
            • 2011-07-21
            • 2018-10-15
            • 2013-01-17
            相关资源
            最近更新 更多