SHFileOperation使目录“不可删除”

时间:2015-01-02 23:26:09

标签: qt winapi win32-process shfileoperation

好的,所以我有一个应用程序在%TEMP%目录中创建一个子目录,以保留一些中间文件,同时应用程序仍在运行。并且,当应用程序即将退出时,我将调用清理函数,以便递归再次从%TEMP%删除我的子目录,因此我们不会留下任何“垃圾”文件背后。到目前为止,这在99.9%的情况下都能正常工作。但在一些非常罕见的情况下,清理功能无法删除我的子目录!测试表明,当清理成功时,它总是在第一次尝试中这样做。并且,当它失败时,在第一次尝试中,重试没有帮助。我可以重试9999次,它会再次失败9999次。无赖!

因此,显然,由于某种原因,“我的”目录被阻止。现在,如果在“my”目录中仍然有一些文件(或子目录),我会理解这一点。但它完全是空的没有文件(甚至不是隐藏的文件!)和没有子目录。只是一个空目录。它仍然可以被删除。为了使事情变得更加模糊,它只能从我自己的过程中删除。它可以从Windows资源管理器或Total Commander完全删除 - 即使我的进程仍在运行。

现在,经过数小时的测试,我发现 SHFileOperation()是罪魁祸首。在某些情况下,我使用 SHFileOperation()文件复制“我的”目录。并且,每当我这样做,这使得该目录“不可删除”的原因未公开!再次说清楚:我通过 SHFileOperation()复制到“my”目录的文件可以完全删除。这是可以更长时间删除的目录 - 仅限于“我的”流程。使用 CopyFile()代替 SHFileOperation()立即解决了问题。我仍然想了解 SHFileOperation()函数发生了什么......

如果有人有想法,我会很高兴知道......

问候!


〜更新#1〜

好的,所以我按要求汇总了一个例子:

#define COPY_METHOD 0

static wchar_t *pathToBuffer(const QString &path)
{
    const QString nativePath = QDir::toNativeSeparators(path);
    wchar_t *buffer = new wchar_t[nativePath.length() + 2];
    wcscpy_s(buffer, path.length() + 2, (const wchar_t*) nativePath.utf16());
    buffer[nativePath.length()] = buffer[nativePath.length() + 1] = L'\0';
    return buffer;
}

bool some_function(const QString &sourceFile, const QString &outputFile, volatile bool *abortFlag)
{
    emit messageLogged(QString("Copy file \"%1\" to \"%2\"").arg(sourceFile, outputFile));

/*---------------------------*/
#if(COPY_METHOD == 0)
/*---------------------------*/

    QScopedArrayPointer<wchar_t> srcBuffer(pathToBuffer(sourceFile));
    QScopedArrayPointer<wchar_t> outBuffer(pathToBuffer(outputFile));

    SHFILEOPSTRUCTW fileOperation;
    memset(&fileOperation, 0, sizeof(SHFILEOPSTRUCTW));

    fileOperation.wFunc  = FO_COPY;
    fileOperation.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_FILESONLY;
    fileOperation.pFrom  = srcBuffer.data();
    fileOperation.pTo    = outBuffer.data();

    emit statusUpdated(0);
    int result = SHFileOperationW(&fileOperation);
    emit statusUpdated(100);

    return (result == 0 && fileOperation.fAnyOperationsAborted == false);

/*---------------------------*/
#elif(COPY_METHOD == 1)
/*---------------------------*/

    emit statusUpdated(0);
    const BOOL success = CopyFile((const wchar_t*) QDir::toNativeSeparators(sourceFile).utf16(), (const wchar_t*) QDir::toNativeSeparators(outputFile).utf16(), FALSE);
    emit statusUpdated(100);

    return (success != FALSE);

/*---------------------------*/
#elif(COPY_METHOD == 2)
/*---------------------------*/

    if(QFile::exists(outputFile))
    {
        QFile::remove(outputFile);
    }

    emit statusUpdated(0);
    QFile src(sourceFile);
    const bool success = src.copy(outputFile);
    emit statusUpdated(100);

    if(!success)
    {
        emit messageLogged(QString("Failed to copy file: %1").arg(src.errorString()));
    }

    return success;

/*---------------------------*/
#else
/*---------------------------*/

#error Invalid copy method specified!

/*---------------------------*/
#endif //COPY_METHOD
/*---------------------------*/
}

方法#0 是我原来做的。而且,一旦我这样做,文件被复制到的目录(而不是文件本身!)将在以后变得“不可删除”。这是可重复的。并且,只要我使用#1 方法或方法#2 (并且没有其他更改完成),问题就解决了...

在第一篇文章中,我说它很少发生。是的,因为(通常)函数“some_function”很少被调用。但是当调用此函数时(或当我强制调用它时),问题就会发生始终 - 至少只要我仍然使用复制方法 #0

最后,当目录可以被删除时,系统返回的错误是一个非常不明确的“访问被拒绝”(Win32错误0x5),所以这没有多大帮助。如果确实发生了,那么 RemoveDirectoy()函数以及 QDir :: rmdir()方法会出现问题。目录为空


〜更新#2〜

好的,这是一个完整的例子,改编自Harry Johnston提供的代码:

#include <Windows.h>
#include <stdio.h>

int main(int argc, char ** argv)
{
    if(!CreateDirectoryW(L"C:\\Users\\MuldeR\\AppData\\Local\\Temp\\umTpRwui9MpP", NULL))
    {
        printf("CreateDirectory Failed: Error %u\n", GetLastError());
        return 1;
    }

    const wchar_t *const sourceFile = L"C:\\Windows\\Media\\ding.wav\0";
    const wchar_t *const outputFile = L"C:\\Users\\MuldeR\\AppData\\Local\\Temp\\umTpRwui9MpP\\test.wav\0";

    SHFILEOPSTRUCTW fileOperation;
    memset(&fileOperation, 0, sizeof(SHFILEOPSTRUCTW));

    fileOperation.wFunc = FO_COPY;
    fileOperation.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_FILESONLY;
    fileOperation.pFrom = sourceFile;
    fileOperation.pTo = outputFile;

    int result = SHFileOperationW(&fileOperation);
    if (result != 0)
    {
        printf("SHFileOperation Failed: Error%u\n", result);
        return 1;
    }

    if(!DeleteFileW(outputFile))
    {
        printf("DeleteFile Failed: Error %u\n", GetLastError());
        return 1;
    }

    if (!RemoveDirectoryW(L"C:\\Users\\MuldeR\\AppData\\Local\\Temp\\umTpRwui9MpP"))
    {
        printf("RemoveDirectory Failed: Error %u\n", GetLastError());
        return 1;
    }

    printf("OK\n");
    return 0;
}

结果是: RemoveDirectory失败:错误5

1 个答案:

答案 0 :(得分:1)

您正在复制的文件具有本地化的shell文件名,可以通过查看源文件夹中的desktop.ini文件看到:

c:\windows\media\desktop.ini

shell复制操作会复制此本地化名称,这意味着:

  • 在目标文件夹

  • 中创建desktop.ini文件
  • 将目标文件夹标记为只读

要删除目录,您需要删除desktop.ini并重置只读标志。

可以手动执行此操作:

#include <Windows.h>
#include <stdio.h>

#define target L"C:\\Users\\MuldeR\\AppData\\Local\\Temp\\umTpRwui9MpP"

int main(int argc, char ** argv)
{
    if(!CreateDirectoryW(target, NULL))
    {
        printf("CreateDirectory Failed: Error %u\n", GetLastError());
        return 1;
    }

    const wchar_t *const sourceFile = L"C:\\Windows\\Media\\ding.wav\0";
    const wchar_t *const outputFile = target L"\\test.wav\0";

    SHFILEOPSTRUCTW fileOperation;
    memset(&fileOperation, 0, sizeof(SHFILEOPSTRUCTW));

    fileOperation.wFunc = FO_COPY;
    fileOperation.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_FILESONLY;
    fileOperation.pFrom = sourceFile;
    fileOperation.pTo = outputFile;

    int result = SHFileOperationW(&fileOperation);
    if (result != 0)
    {
        printf("SHFileOperation Failed: Error%u\n", result);
        return 1;
    }

    if(!DeleteFileW(outputFile))
    {
        printf("DeleteFile(outputFile) Failed: Error %u\n", GetLastError());
        return 1;
    }

    if (!DeleteFileW(target L"\\desktop.ini"))
    {
        printf("DeleteFile(desktop.ini) Failed: Error %u\n", GetLastError());
        return 1;
    }

    if (!SetFileAttributes(target, 0))
    {
        printf("SetFileAttributes Failed: Error %u\n", GetLastError());
        return 1;
    }

    if (!RemoveDirectoryW(target))
    {
        printf("RemoveDirectory Failed: Error %u\n", GetLastError());
        return 1;
    }

    printf("OK\n");
    return 0;
}

但是既然你用shell创建了目标目录,那么以相同的方式删除它会更加一致,当然这会为你处理细节:

#include <Windows.h>
#include <stdio.h>

#define target L"C:\\Users\\MuldeR\\AppData\\Local\\Temp\\umTpRwui9MpP"

int main(int argc, char ** argv)
{
    if(!CreateDirectoryW(target, NULL))
    {
        printf("CreateDirectory Failed: Error %u\n", GetLastError());
        return 1;
    }

    const wchar_t *const sourceFile = L"C:\\Windows\\Media\\ding.wav\0";
    const wchar_t *const outputFile = target L"\\test.wav\0";

    SHFILEOPSTRUCTW fileOperation;
    memset(&fileOperation, 0, sizeof(SHFILEOPSTRUCTW));

    fileOperation.wFunc = FO_COPY;
    fileOperation.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_FILESONLY;
    fileOperation.pFrom = sourceFile;
    fileOperation.pTo = outputFile;

    int result = SHFileOperationW(&fileOperation);
    if (result != 0)
    {
        printf("SHFileOperation Failed: Error%u\n", result);
        return 1;
    }

    memset(&fileOperation, 0, sizeof(SHFILEOPSTRUCTW));

    fileOperation.wFunc = FO_DELETE;
    fileOperation.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
    fileOperation.pFrom = target L"\0";

    result = SHFileOperationW(&fileOperation);
    if (result != 0)
    {
        printf("SHFileOperation Failed: Error%u\n", result);
        return 1;
    }

    printf("OK\n");
    return 0;
}