C++/Win32:如何等待挂起的删除完成

Posted

技术标签:

【中文标题】C++/Win32:如何等待挂起的删除完成【英文标题】:C++/Win32: How to wait for a pending delete to complete 【发布时间】:2011-04-15 09:52:55 【问题描述】:

已解决:

可行的解决方案:sbi's answer 真实情况的解释:Hans's answer 解释为什么 OpenFile 不通过“DELETE PENDING”:Benjamin's answer

问题:

我们的软件在很大程度上是专有脚本语言的解释器引擎。该脚本语言能够创建文件、处理文件,然后删除文件。这些都是独立的操作,在这些操作之间没有文件句柄保持打开状态。

(即在文件创建期间,创建一个句柄,用于写入,然后关闭。在文件处理部分,一个单独的文件句柄打开文件,从中读取,并在 EOF 时关闭。而 最后,delete 使用 ::DeleteFile,它只使用文件名,根本没有文件句柄)。

最近我们开始意识到,一个特定的宏(脚本)有时无法在随后的某个随机时间创建文件(即它在“创建、处理、删除”的前一百次迭代中成功,但是当它回到第一次创建它时,Windows 会回复“拒绝访问”)。

更深入地研究这个问题,我编写了一个非常简单的程序来循环这样的事情:

while (true) 
    HANDLE hFile = CreateFileA(pszFilename, FILE_ALL_ACCESS, FILE_SHARE_READ,
                               NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
        return OpenFailed;

    const DWORD dwWrite = strlen(pszFilename);
    DWORD dwWritten;

    if (!WriteFile(hFile, pszFilename, dwWrite, &dwWritten, NULL) || dwWritten != dwWrite)
        return WriteFailed;

    if (!CloseHandle(hFile))
        return CloseFailed;

    if (!DeleteFileA(pszFilename))
        return DeleteFailed;

如您所见,这直接指向 Win32 API,而且非常简单。我创建一个文件,写入它,关闭句柄,删除它,冲洗,重复......

但是在某个地方,我会在 CreateFile() 调用期间收到拒绝访问 (5) 错误。查看 sysinternal 的 ProcessMonitor,我可以看到根本问题是在我尝试再次创建文件时,文件上有一个挂起的删除。

问题:

有没有办法等待删除完成? 有没有办法检测文件正在等待删除?

我们已经尝试了第一个选项,只需在 HFILE 上使用 WaitForSingleObject()。但是 HFILE 总是在 WaitForSingleObject 执行之前关闭,所以 WaitForSingleObject 总是返回 WAIT_FAILED。显然,尝试等待关闭的句柄是行不通的。

我可以等待文件所在文件夹的更改通知。但是,对于仅偶尔出现的问题来说,这似乎是一个极其开销密集的杂项(即:在我的 Windows 7 x64 E6600 上的测试中PC 它通常在迭代 12000+ 时失败——在其他机器上,它可能在迭代 7 或 15 或 56 时立即发生,或者永远不会发生)。

我无法辨别任何明确允许该以太的 CreateFile() 参数。无论 CreateFile 有什么参数,当文件等待删除时,打开文件以供 any 访问确实是不行的。

由于我在 Windows XP 机器和 x64 Windows 7 机器上都可以看到这种行为,因此我很确定这是 Microsoft“按预期”的核心 NTFS 行为。因此,我需要一个解决方案,允许操作系统在我尝试继续之前完成删除,最好不要不必要地占用 CPU 周期,并且不会因查看该文件所在的文件夹而产生极大的开销(如果可能的话)。

1是的,这个循环在写入失败或关闭失败时返回哪个泄漏,但是由于这是一个简单的控制台测试应用程序,应用程序本身退出,Windows保证所有句柄都被操作系统关闭当一个过程完成时。所以这里不存在泄漏。

bool DeleteFileNowA(const char * pszFilename)

    // Determine the path in which to store the temp filename
    char szPath[MAX_PATH];
    strcpy(szPath, pszFilename);
    PathRemoveFileSpecA(szPath);

    // Generate a guaranteed to be unique temporary filename to house the pending delete
    char szTempName[MAX_PATH];
    if (!GetTempFileNameA(szPath, ".xX", 0, szTempName))
        return false;

    // Move the real file to the dummy filename
    if (!MoveFileExA(pszFilename, szTempName, MOVEFILE_REPLACE_EXISTING))
        return false;

    // Queue the deletion (the OS will delete it when all handles (ours or other processes) close)
    if (!DeleteFileA(szTempName))
        return false;

    return true;

【问题讨论】:

您确定所有句柄都已关闭?因为您在 MSDN 上准确描述了您所写的内容:“如果您对因先前调用 DeleteFile 而等待删除的文件调用 CreateFile,则该函数将失败。操作系统会延迟文件删除,直到该文件的所有句柄都关闭.GetLastError 返回 ERROR_ACCESS_DENIED。" 我创建了一个控制台应用程序,它在循环中执行上述代码。它最终失败了……通常在迭代 12000-15000 左右。所以除非你看到上面 8 行代码泄漏了句柄,否则我认为这是不可能的(至少对于这个测试小程序来说)。 @Mordachai:我已经看到这个并删除了我的评论。 我遇到了这个由Windows Search服务引起的问题,它会注意到我创建了一个目录并锁定它以对其进行索引,同时我试图删除它并得到错误5。很容易通过在启用索引器的循环中创建和删除目录/文件来重现此问题。 MsMpEng in my case 阻止了该文件。我发现 MS 确认 index/antivirus SW can block the file。现在,我因此习惯于在重试循环中删除或更好地创建新的临时文件而不是重复使用单个名称,希望单个删除最终会生效。现在,我遇到了类似的问题。 Gcc p2gcc p.c > p.exe && p && gcc p2.c > p.exe && p ... 中失败,因为刚刚终止的 p.exe 被阻止,filemon 中没有任何内容,但 p 进程访问该文件。 【参考方案1】:

先重命名要删除的文件,再删除。

使用GetTempFileName() 获取唯一名称,然后使用MoveFile() 重命名文件。然后删除重命名的文件。如果实际删除确实是异步的并且可能与创建同一文件发生冲突(正如您的测试似乎表明的那样),这应该可以解决问题。

当然,如果您的分析是正确的并且文件操作发生了一些异步,这可能会引入您在重命名完成之前尝试删除文件的问题。但是你可以一直尝试在后台线程中删除。

如果 Hans 是对的(而且我倾向于相信他的分析),那么移动可能并没有真正的帮助,因为您可能无法真正重命名由另一个进程打开的文件。 (但你可能会,我不知道。)如果确实如此,我能想出的唯一其他方法就是“继续尝试”。您将不得不等待几毫秒然后重试。当这没有帮助时,保持超时放弃。

【讨论】:

好主意。我不确定它是否适用于我们的真实场景(这比我的问题复杂得多),但它可能! :) @Mordachai:您可能想阅读我的第二次编辑。如果汉斯是对的,这可能无济于事。但是你总是可以先在你的测试程序中尝试一下,看看让你的真实应用程序复杂化是否真的值得一试。 +1。汉斯是否正确可能并不重要。您可以重命名打开的文件。这没有问题,因为文件句柄大多独立于文件名。该名称仅在您从文件名创建文件句柄时才重要。带有打开文件句柄的假设进程不会受到影响。 这似乎有效!基本上,我正在创建一个临时文件(实际上是通过 GetTempFileName 创建它),然后使用 MoveFileEx 用真实文件覆盖唯一的临时文件,然后在临时文件名上发出 DeleteFile。到目前为止,我不能让它出错。我预计性能会受到影响 - 完成所有这些额外开销的速度不会那么快,但为了使我们的 scipter 免受多任务文件系统的微妙影响,这可能是值得的。 @Mordachai: GetTempFileName() 必须 创建文件,否则在您获得唯一文件名和实际创建文件。无论如何,很高兴你能成功。【参考方案2】:

如果 CreateFile 返回 INVALID_HANDLE_VALUE,那么您应该确定 GetLastError 在您的特定情况下返回什么(待删除)并仅根据该错误代码循环回 CreateFile。

FILE_FLAG_DELETE_ON_CLOSE 标志可能会给您带来一些好处。

【讨论】:

我认为 Windows 回复“拒绝访问”GetLastError() 返回的内容。 GetLastError() 始终是“(5) Access Denied”——这是一个相当蹩脚的、来自操作系统的有限响应。如果是“(91929)待删除”,那就太好了! :) 感谢您的想法,菠萝。不幸的是,脚本引擎无法知道脚本何时会删除目标文件(如果有的话),因此它无法在脚本要求删除之前将文件标记为删除。一旦这确实发生了,我们就要靠操作系统来完成操作了。如果脚本尝试执行另一个再次生成该文件的命令并且之前的命令尚未完成,那么他们将收到此错误。剧本作者的所作所为并不是我能真正控制的(从表面上看,这个剧本是合理的)【参考方案3】:

Windows 中还有其他进程需要该文件的一部分。搜索索引器是一个明显的候选者。或者病毒扫描程序。他们将打开文件以进行完全共享,包括 FILE_SHARE_DELETE,因此其他进程不会因他们打开文件而受到严重影响。

这通常效果很好,除非您以高速率创建/写入/删除。删除将成功,但文件不能从文件系统中消失,直到它的最后一个句柄关闭。例如,搜索索引器持有的句柄。任何试图打开该挂起删除文件的程序都会被错误 5 拍打。

这在多任务操作系统上是一个普遍问题,您无法知道其他进程可能想要弄乱您的文件。您的使用模式似乎不寻常,请先查看。一种解决方法是捕获错误,休眠并重试。或者使用 SHFileOperation() 将文件移动到回收站。

【讨论】:

是的,完全避免这种模式可能是明智的!大声笑 - 不幸的是,我的角色类似于语言开发人员,他拥有使用所述语言的程序员。我可以告诉他们,最好避免一些模式,但最终,他们编写了他们的软件(脚本),我必须尽我所能处理他们关注“最佳实践”的有限能力。 好吧,试图对那些脚本程序员隐藏真正的操作系统行为将是一个严重的错误。改进你的api,为那些卑微的灵魂提供一种自己处理问题的方法。错误代码,异常,等等。避免上帝的态度,你只会创造出每个人都认为会很糟糕的东西。因为他们的脚本存在问题,如果没有诊断就无法工作,而且上帝不会回复他们的电子邮件。 在 C/C++/Java/任何其他主要编程语言的级别上,我认为您是对的。我的直觉完全同意你的看法。然而,在“简化脚本语言”的层面上——我们通常信奉“如果脚本编写者做的事情是合理的,那么引擎应该让它发生”。一个循环涉及一个生成给定文件的子组件,另一个处理然后删除文件,然后循环这两个块以获得更大的收益可以说是合理。所以在这种情况下,我们可能会向脚本编写者隐藏这种复杂性。 公平地说,这是特定于 windows 的问题,而不是一般的操作系统问题。其他操作系统通过允许其等效的 DeleteFile 删除名称而不删除文件来轻松处理此问题,因此索引器不会阻止文件被删除。 UNIX 系统就是这种行为的一个例子。问题的存在是因为 Win32(不是 NTFS)要求每个打开的文件都有一个名称。 此类服务(如搜索索引器)是否也会尝试读取临时文件?【参考方案4】:

我认为这只是文件系统的糟糕设计。当我使用通信端口打开/关闭它们时,我也遇到过同样的问题。

不幸的是,我认为最简单的解决方案是在收到INVALID_HANDLE_VALUE 后多次重试创建文件。 GetLastError() 还可能为您提供更好的方法来检测这个特定的 INVALID_HANDLE_VALUE

我更喜欢重叠 I/O,但他们的 CloseHandle()DeleteFile() 不处理重叠操作 :(

【讨论】:

【参考方案5】:

愚蠢的建议 - 因为它很少失败,所以只需等待几毫秒失败再试一次。

或者,如果延迟很重要,请切换到另一个文件名,让旧文件稍后删除。

【讨论】:

【参考方案6】:

由于您正在创建一个新文件,对其进行处理,然后删除它,听起来您并不真正关心文件名是什么。如果确实如此,您应该考虑始终创建一个临时文件。这样,每次执行此过程时,您都不必关心文件尚未被删除。

【讨论】:

-1 他的问题表明他无法控制导致此问题的脚本内容 @Elemental,没错。他说的是脚本的content,而不是脚本的location,所以downvote无效。 @Jocab - 不幸的是,脚本本身包含命令,包括直接使用文件名的删除命令(字面意思),他不能将脚本更改为使用临时文件名。 这是个好主意,Jacob,但实际上我们的脚本编写者可以控制文件的名称。尽管出现了这种特殊情况,但总的来说,它没有出现。在语言级别(脚本解释器)不可能假设文件的性质,并围绕它强制执行任何特定规则(它只是一个上下文中的输出文件名,另一个上下文中的输入文件名,以及删除命令的目标在另一个,据解释器所知,所有无关)。 脚本解释器可以在文件系统上创建唯一命名的文件,并维护脚本中使用的名称到实际文件系统名称的映射。脚本完成后,解释器可以将剩余文件重命名为脚本所需的名称,以创建适当的副作用。【参考方案7】:

有没有办法检测文件正在等待删除?

将GetFileInformationByHandleEx 函数与FILE_STANDARD_INFO 结构一起使用。

但该功能无法解决您的问题。 sbi's solution 两者都没有。

【讨论】:

呃,我不认为这是可能的 [在我们的情况下]。坦率地说,无法获得待删除文件的句柄。因此,当 CreateFile() 执行时,该文件处于待删除状态,但无法获得对其进行检查的句柄。 :( 一旦文件挂起为删除,您将无法再次打开该文件。只有在文件删除之前获得另一个句柄时才有可能。 正确 - 所以任何有用的解决方案都需要在我调用 CreateFile() 时回答“为什么访问被拒绝”这个问题?如果有办法在那个时间点询问操作系统“文件是否挂起删除”,那么这将很有用。或者,如果有一种方法可以让 CreateFile() 提供更多详细信息而不是空白的“拒绝”,那么这将更有用。 最终,除非有人可以告诉我为什么 MS 未能报告更详细的信息或将这些恶作剧隐藏在操作系统的掩护下,否则我不得不得出结论,这是一个非常愚蠢、糟糕的微软的深思熟虑的机制。我不反对被滥用 - 如果有一个真正好的理由,我会全力以赴。但到目前为止,这似乎是 NTFS 和 Win32API 团队部分设计决策考虑不周的结果。 在内核模式下,NtCreateFile 返回 STATUS_DELETE_PENDING。可能是你想要的。但是 I/O 管理器在返回 CreateFile 之前将错误转换为 ERROR_ACCESS_DENIED。 support.microsoft.com/kb/113996我不知道他们为什么不把它翻译成ERROR_DELETE_PENDING。当您打开文件并在关闭您打开的句柄之前再次删除和删除时,您可以看到 ERROR_DELETE_PENDING。【参考方案8】:

这可能不是您的特殊问题,但有可能,所以我建议您离开 Process Monitor (Sysinternals) 看看。

我遇到了完全相同的问题,并发现 Comodo Internet Security (cmdagent.exe) 导致了该问题。以前我有一台双核机器,但是当我升级到 Intel i7 时,突然我的工作软件(Perfore 软件的jam.exe)不再工作,因为它具有相同的模式(删除然后创建,但没有检查)。调试问题后,我发现 GetLastError() 正在返回拒绝访问,但 Process Monitor 显示“删除挂起”。这是跟踪:

10:39:10.1738151 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Desired Access: Read Attributes, Delete, Disposition: Open, Options: Non-Directory File, Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
10:39:10.1738581 AM jam.exe 5032    QueryAttributeTagFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Attributes: ANCI, ReparseTag: 0x0
10:39:10.1738830 AM jam.exe 5032    SetDispositionInformationFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Delete: True
10:39:10.1739216 AM jam.exe 5032    CloseFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1739438 AM jam.exe 5032    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1744837 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1788811 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1838276 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1888407 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1936323 AM System  4   FASTIO_ACQUIRE_FOR_SECTION_SYNCHRONIZATION  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS SyncType: SyncTypeOther
10:39:10.1936531 AM System  4   FASTIO_RELEASE_FOR_SECTION_SYNCHRONIZATION  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1936647 AM System  4   IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1939064 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   DELETE PENDING  Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0
10:39:10.1945733 AM cmdagent.exe    1188    CloseFile   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1946532 AM cmdagent.exe    1188    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1947020 AM cmdagent.exe    1188    IRP_MJ_CLOSE    C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS 
10:39:10.1948945 AM cfp.exe 1832    QueryOpen   C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   FAST IO DISALLOWED  
10:39:10.1949781 AM cfp.exe 1832    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   NAME NOT FOUND  Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a
10:39:10.1989720 AM jam.exe 5032    CreateFile  C:\Users\Dad\AppData\Local\Temp\jam5032t1.bat   SUCCESS Desired Access: Generic Write, Read Attributes, Disposition: OverwriteIf, Options: Synchronous IO Non-Alert, Non-Directory File, Attributes: N, ShareMode: Read, Write, AllocationSize: 0, OpenResult: Created

如您所见,jam.exe 发出了删除请求,随后多次尝试再次打开文件(这是一个循环中的fopen)。您可以看到cmdagent.exe 可能在关闭句柄时打开了文件,然后突然jam.exe 现在能够打开文件。

当然,建议的解决方案是等待再试一次,效果很好。

【讨论】:

是的,这是相同的模式。在我的例子中,它是 NOD32 Antivirus,但总的来说,这个问题(可能)存在于所有 Windows 机器上(可能还有其他操作系统,取决于它们如何处理文件系统)。这不一定是操作系统中的设计缺陷(尽管他们为什么不做我所做的[删除后立即释放目录条目,即使句柄仍然打开]我不知道)。 UPSHOT:软件需要始终写入“等待并重试”或标记为“已回答”重命名然后删除。 顺便说一句——这对 Raymond Chen 来说是一个很棒的话题。太糟糕了,我不能把这个加入他的队列:( devblogs.microsoft.com/oldnewthing/20120907-00/?p=6663【参考方案9】:

在 Windows Vista/Windows 7 上,DeleteFileTransacted 使用事务删除文件,确保它们被删除(刷新文件缓冲区等)。但是,对于 Windows XP 兼容性,这不是一个选项。

如何做到这一点的另一个想法是使用带有标志 OF_CREATE 的 OpenFile(),如果文件存在则将长度设置为零,如果文件不存在则创建它,然后在文件句柄上调用 FlushFileBuffers 以等待这个操作(使文件零长度)完成。完成后,文件大小为 0,然后只需调用 DeleteFile。

您可以稍后测试该文件是否存在或者它的长度是否为零,以同样的方式处理它。

【讨论】:

DeleteFileTransacted 实际上分享了DeleteFile 的行为,即在关闭最后一个句柄时文件将被删除。引用:“DeleteFileTransacted 函数在关闭时标记要删除的文件。”来自msdn.microsoft.com/en-us/library/windows/desktop/…【参考方案10】:

实际上我在使用 LoadLibrary(path) 时遇到了同样的问题。我无法删除 path 中的文件。

解决方案是“关闭句柄”或使用 FreeLibrary(path) 方法。

注意:请阅读MSDN 上关于 FreeLibrary() 的“备注”。

【讨论】:

【参考方案11】:

根据[1],您可以使用NtDeleteFile 来避免DeleteFile 的异步性质。 [1] 还提供了有关 DeleteFile 工作原理的一些详细信息。

很遗憾,NtDeleteFile [2] 上的官方文档没有提及有关此问题的任何具体细节。

[1] Undocumented functions of NTDLL

[2] ZwDeleteFile function

【讨论】:

这是错误的。 NtDeleteFile 仅通过跳过两步 NtOpenFileNtSetInformationFile 序列并且不需要 I/O 管理器分配真正的 File 对象,从而使设置删除处置更有效。当然,它仍然需要为 set-information 请求分配 IRP 并调用文件系统驱动程序,当然文件仍然不会被取消链接,直到最后一个文件引用被关闭/清理。【参考方案12】:

最佳答案是given by sbi,但为了完整起见,有些人可能还想了解 Windows 10 RS1/1603 现在提供的一种新方法。

它涉及使用 FileDispositionInfoEx 类调用 SetFileInformationByHandle API,并设置标志 FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS。请参阅full answer by RbMm。

【讨论】:

【参考方案13】:

我刚刚遇到了这个确切问题,并采取了两个步骤来解决它; 我停止使用 C/C++ stdlib apis 和 ::DeleteFile(..),并切换到:

    ::MoveFileEx(src,dest,MOVEFILE_WRITE_THROUGH);。 See: MOVEFILE_WRITE_THROUGH

    h = ::CreateFile(DELETE | SYNCHRONIZE,OPEN_EXISTING,FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_OPEN_REPARSE_POINT); ::CloseHandle(h);

以上是显示相关标志的伪调用,特别注意用于实现删除的 CreateFile 调用没有共享。

他们一起让我在 renamedelete 语义上更加精确。 他们现在正在我的代码中工作,并提高了其他线程/进程的精度和控制(观察文件系统的更改)由于其他重命名和/或中的延迟[或共享]而对文件插入操作删除 API。如果没有该控件,则在关闭其最后一个内核句柄时设置为删除的文件实际上可能会在系统重新启动之前缓慢打开,而您可能不知道。

希望这些反馈 sn-ps 可能对其他人有用。

附录:我碰巧在我所做的部分工作中使用了硬链接。事实证明,尽管您可以在打开的文件上创建硬链接,但在关闭该 NTFS 文件的任何底层数据流的所有句柄之前,您不能删除其中的任何一个。这很奇怪,因为:

操作系统使用单个 ID 跟踪它们,这实际上是一个 INode (ntfs uid)。 https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-openfilebyid https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilenamew https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_id_info 您可以在打开时创建硬链接,但不要删除它们 硬链接实际上是 NTFS 中属性 (XATTR) 中的别名 您可以在打开时重命名它们 所以改名没关系

这会导致您认为只有 最后一个 硬链接应该是不可删除的,而内核具有一个或多个引用硬链接 NTFS 文件的 MFT-Entry/ATTR 的打开文件句柄。 无论如何,只是另一件事要知道。

【讨论】:

您是先调用 MoveFileEx 再调用 CreateFile 吗?对我来说,它不会在关闭时删除文件,不知道为什么。

以上是关于C++/Win32:如何等待挂起的删除完成的主要内容,如果未能解决你的问题,请参考以下文章

错误:无法 ALTER TABLE,因为它在尝试删除表中的列时有挂起的触发器事件

进程的阻塞和挂起的区别

进程的阻塞和挂起的区别

解决win7下安装SQL2000时以前的某个程序安装已在安装计算机上创建挂起的文件操作。运行安装程序之前必须重新启动计算机!

升级Win10后删除Windows.old提示需要SYSTEM权限怎么办?

升级Win10后删除Windows.old提示需要SYSTEM权限怎么办