Windows C ++:文件夹移动访问被拒绝错误

时间:2018-10-05 13:24:57

标签: c++ windows winapi access-denied movefileex

下面的代码是我项目的摘录,其中包括:

  1. 从指定的本地文件夹(“上载”文件夹)获取文件夹内容的集合
  2. 将包含的文件复制到远程网络文件夹。如果存在子文件夹,则会复制其文件,但不会保留文件夹结构。
  3. 将原始本地文件夹移动到其他本地位置(“存档”文件夹)

我遇到的麻烦在于移动操作。如果要移动的文件夹包含一个或多个子文件夹,则移动将失败,并显示“拒绝访问”错误。我将原因缩小到先前的函数调用,该函数生成要复制的文件列表。我在下面的代码中突出显示了此调用。它在main中(请参阅对getFolderItemsRecursive()的首次调用)。我不明白为什么此递归函数会产生问题。好像是通过收集信息以某种方式将某物标记为“使用中”。请注意,我实际上并没有复制文件,因为我已经注释掉了这些行。事实并非如此。

要运行下面的代码,您需要在几个位置创建几个文件夹,然后在代码中输入位置。该代码假定c:_UploadTests和c:_archivedtests。要重现此问题,请在_UploadTest中至少创建一个带有其自己的子文件夹的子文件夹。例如。 c:_UploadTests \ Foo \ Bar \

#include "windows.h"
#include "stdafx.h"
#include <cstdint>
#include <vector>

#define RECURSION_DEPTH_NON 0
#define RECURSION_DEPTH_FULL -1

// Object needed bacause WIN32_FIND_DATAA doesn't contain file path data and we need this with each object.
typedef struct fileObject{
    std::string path;
    WIN32_FIND_DATAA metaData;
} fileObject;

void getFolderItems(std::vector<WIN32_FIND_DATAA>& output, const std::string& path) {
    WIN32_FIND_DATAA findfiledata;
    HANDLE hFind = INVALID_HANDLE_VALUE;

    char fullpath[MAX_PATH];
    GetFullPathNameA(path.c_str(), MAX_PATH, fullpath, 0);
    std::string fp(fullpath);

    hFind = FindFirstFileA((fp + "\\*").c_str(), &findfiledata);
    if (hFind != INVALID_HANDLE_VALUE) {
        do {
            switch (findfiledata.dwFileAttributes) {
            case FILE_ATTRIBUTE_DIRECTORY:                  // Its a directory
                if (findfiledata.cFileName[0] != '.')
                    output.push_back(findfiledata);
                break;
            case FILE_ATTRIBUTE_NORMAL:                     // Its a file
            case FILE_ATTRIBUTE_ARCHIVE:
            case FILE_ATTRIBUTE_COMPRESSED:
                output.push_back(findfiledata);
                break;
            }
        } while (FindNextFileA(hFind, &findfiledata) != 0);
    }
}

/// Gets a list of directory contents and their sub folders under a specified path
/// @param[out] output Empty vector to be filled with result
/// @param[in]  path   Input path, may be a relative path from working dir
/// @param[in]  depth  Max depth of recursion relative to 'path'. -1 = full recursion, 0 = non, 1 = allow one level down, etc
void getFolderItemsRecursive(std::vector<fileObject>& output, const std::string& path, int depth) {
    std::vector<WIN32_FIND_DATAA> thisLvl;
    fileObject fileObj;

    // Get all the items from this level folder
    getFolderItems(thisLvl, path);

    // Loop through the items and dive deeper into any subfolders
    for (std::vector<WIN32_FIND_DATAA>::iterator i = thisLvl.begin(); i != thisLvl.end(); ++i) {
        // Add the current folder to the object list
        fileObj.metaData = *i;
        fileObj.path = path;
        output.push_back(fileObj);

        if (fileObj.metaData.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY) {
            if (depth == RECURSION_DEPTH_FULL)
                getFolderItemsRecursive(output, path + std::string("\\") + fileObj.metaData.cFileName, RECURSION_DEPTH_FULL);
            else if (depth > 0)
                getFolderItemsRecursive(output, path + std::string("\\") + fileObj.metaData.cFileName, depth--);
        }
    }
}


int main(int argc, char** argv) {
    // manually create the list of upload folders
    std::vector<std::string> uploadFoldersVector;
    uploadFoldersVector.push_back("c:\\_UploadTests");

    // For each upload folder, get a list of sub folders and loop through them copying out the files.
    for (uint8_t i = 0; i < uploadFoldersVector.size(); i++) {
        std::string srcPath;
        std::string dstPath;
        BYTE flags;

        // Get a list of this folders contents
        std::vector<fileObject> uploadFiles;

        /*********************
        The function below seems to be the culprit when called with RECURSION_DEPTH_FULL - look in all subfolders and their children.
        A similar call but with RECURSION_DEPTH_NON doesn't cause any issues.
        **********************/
        getFolderItemsRecursive(uploadFiles, uploadFoldersVector[0], RECURSION_DEPTH_FULL);

        // For each file object, copy it to the network archive.
            // Removed for StackOverflow for simplicity - adds nothing to the problem definition

        // Finally move this folder into the local archive folder
            // Get a list of immediate sub folders
            uploadFiles.clear();
            getFolderItemsRecursive(uploadFiles, uploadFoldersVector[0], RECURSION_DEPTH_NON);

            flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING;

            for (uint8_t j = 0; j < uploadFiles.size(); j++) {
                srcPath = uploadFoldersVector[0] + "\\" + uploadFiles[j].metaData.cFileName;
                dstPath = "c:\\_archivedtests" + (std::string)uploadFiles[j].metaData.cFileName;

                if (!MoveFileExA(srcPath.c_str(), dstPath.c_str(), flags)) {
                    fprintf(stderr, "Error moving folder %s to local archive. \n\tWindows returned code: %ld\n", srcPath.c_str(), GetLastError());
                }
            }
    }

    getchar();
    return 0;
}

1 个答案:

答案 0 :(得分:0)

getFolderItems()在迭代循环结束后没有调用FindClose(),因此您将打开的句柄泄漏到正在迭代的文件夹中,这就是为什么您将它们视为“正在使用”。

if (hFind != INVALID_HANDLE_VALUE) {
    do {
        ...
    } while (FindNextFileA(hFind, &findfiledata) != 0);
    FindClose(hFind); // <-- ADD THIS!
}

getFolderItems()也有其他错误:

  • if (findfiledata.cFileName[0] != '.')是错误的使用检查方法,因为除了...以外,可能还存在以.开头的文件夹,因此您需要使用这种检查:

    if ((strcmp(findfiledata.cFileName, ".") != 0) && (strcmp(findfiledata.cFileName, "..") != 0))
    
  • 使用switch检查属性是错误的,因为条目可能存在多个属性。您需要使用按位AND(&)运算符检查特定属性:

    if (findfiledata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
    {
        // Its a directory
        ...
    }
    else
    {
        // Its a file
        if (findfiledata.dwFileAttributes & (FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_COMPRESSED))
            ...
    }
    

此外,不要使用MoveFileExA()SHFileOperation()来移动单个文件,而可以使用一次操作来移动多个文件。