FindFirstFile / FindNextFile不返回文件夹

时间:2017-02-23 00:20:24

标签: c++ windows winapi mfc

注意:建议重复的问题讨论了CreateFileERROR_FILE_NOT_FOUND,在文件上有现有句柄,以及标记要在以后删除的文件。虽然这是一个类似的主题,但这些问题都与我的案例无关。

我有以下方法删除目录。我已经使用了一段时间没有问题。

但是最近,我确实通过网络路径删除目录和文件(只是连接到我的路由器的USB驱动器)来完成它的步伐。

除了一个没有删除目录中所有文件的区域外,所有内容似乎都运行良好,因此RemoveDirectory()失败。 (我已将每个文件添加到列表中并验证该列表不包含未删除的文件。)

所有文件的命名都非常相似,并且没有返回或删除的文件名称没有异常。如果我再次运行程序,它将删除剩余的文件,然后在另一个目录上稍后出现相同的错误。

bool CBackupWorker::DeleteDirectory(LPCTSTR lpszName)
{
    if (!DirectoryExists(lpszName))
    {
        ASSERT(false);  // Unexpected
        return true;
    }

    CFileFind find;
    BOOL bContinue = find.FindFile(AppendPath(lpszName, _T("*")));
    while (bContinue)
    {
        bContinue = find.FindNextFile();
        if (find.IsDirectory())
        {
            if (!find.IsDots())
            {
                if (!DeleteDirectory(find.GetFilePath()))
                    return false;
            }
        }
        else
        {
            if (find.IsReadOnly())
                ClearReadOnlyAttribute(find);
            if (!::DeleteFile(ConvertToExtendedLengthPath(find.GetFilePath())))
            {
                LogErrorV(::GetLastError(), _T("ERROR DELETING FILE : '%s'"), (LPCTSTR)find.GetFilePath());
                return false;
            }
        }
    }

    CString sPath = ConvertToExtendedLengthPath(lpszName);
    DWORD dwAttributes = ::GetFileAttributes(sPath);
    if (dwAttributes != INVALID_FILE_ATTRIBUTES && (dwAttributes & FILE_ATTRIBUTE_READONLY))
        ::SetFileAttributes(sPath, dwAttributes & (DWORD)~FILE_ATTRIBUTE_READONLY);
    if (!::RemoveDirectory(sPath))
    {
        LogErrorV(::GetLastError(), _T("ERROR DELETING DIRECTORY : '%s'"), lpszName);
        return false;
    }
    return true;
}

关于代码的几点说明:ConvertToExtendedLengthPath()添加了一个前缀,以便允许超过MAX_PATH的路径;但是,尽管这些名称相当长,但它们都不超过MAX_PATH。 (在这种情况下,该方法只返回输入值。)

此外,我正在删除具有此属性的文件和目录的只读属性。但同样,我已经确认这不会在我正在使用的任何文件上发挥作用。

最后,这不是我正在使用的文件正在发生变化的情况。我是唯一一个可以访问此外部驱动器的人。

有没有人看到FindFirstFile / FindNextFile会遗漏几个文件的情况?或者通过网络共享访问文件可能会干扰这些功能的行为?

2 个答案:

答案 0 :(得分:2)

在使用FindFirstFile / FindNextFile组合时,我个人没有观察到这种奇怪的行为。

但是,如果你想删除一个目录及其内容,这里有一个解决方案:

// VadaPoché_SO.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include <iostream>
#include <strsafe.h>
#include <Shobjidl.h>

HRESULT CreateAndInitializeFileOperation(REFIID riid, void **ppv) //this function is copied verbatim from https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/winui/shell/appplatform/fileoperations/FileOperationSample.cpp
{
    *ppv = NULL;
    // Create the IFileOperation object
    IFileOperation *pfo;
    HRESULT hr = CoCreateInstance(__uuidof(FileOperation), NULL, CLSCTX_ALL, IID_PPV_ARGS(&pfo));
    if (SUCCEEDED(hr))
    {
        // Set the operation flags.  Turn off  all UI
        // from being shown to the user during the
        // operation.  This includes error, confirmation
        // and progress dialogs.
        hr = pfo->SetOperationFlags(FOF_NO_UI);
        if (SUCCEEDED(hr))
        {
            hr = pfo->QueryInterface(riid, ppv);
        }
        pfo->Release();
    }
    return hr;
}

int main()
{
    using namespace std;

    const wchar_t *dirFullPath = L"C:\\test1\\test"; //this directory, and its contents, if any, will be deleted.

    IShellItem* itemDirToDelete = NULL;
    IFileOperation *fileOp = NULL;
    HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);

    if (FAILED(hr))
    {
        cout << "CoInitializeEx failed. Error code returned: 0x" << hex << hr << endl;
        return -1;
    }

    if (FAILED(SHCreateItemFromParsingName(dirFullPath, NULL, IID_PPV_ARGS(&itemDirToDelete))))
    {
        cout << "SHCreateItemFromParsingName failed. Error code returned: 0x" << hex << hr << endl;
        return -1;
    }

    if (SUCCEEDED(CreateAndInitializeFileOperation(IID_PPV_ARGS(&fileOp))))
    {
        //Note: contrary to its name DeleteItem, this does NOT do the actual deletion. 
        //It only declares an intention to perform deletion.
        if (SUCCEEDED(fileOp->DeleteItem(itemDirToDelete, NULL))) 
        {
            hr = fileOp->PerformOperations(); //This is the statement that acts on the intention declared above. i.e. it deletes the folder.
        }
        fileOp->Release();
    }

    return 0;
}

答案 1 :(得分:2)

虽然Windows文件系统和SMB的Windows实现都确保files aren't left out of a directory enumeration, even if the contents of the directory are changing,这似乎不是SMB protocol itself的要求。 (但我不是专家,所以我可能忽略了一些东西。)无论如何,无论服务器的行为在技术上是否正确,你都可能需要按原样处理它。

我猜想shell API已经处理了这种情况,但在你的情况下我建议不要使用它,因为我不相信它支持长路径名。

如果您知道任何给定目录中永远不会有过多的条目,并且在您枚举它的同时没有其他进程将删除目录中的文件,您可能更愿意阅读该列表首先是文件,然后开始删除它们。我知道你已经在这些方面建立了一个概念验证。

时间效率稍低但内存效率更高,我认为更强大的方法是同时枚举和删除文件(如已发布的代码中所示),但随后循环并重新枚举,直到除了...条目之外,您发现该目录为空。唯一明显的缺点是额外的服务器往返。因人而异。 : - )