如何在C++中让我的程序监视文件修改?

87

例如,Visual Studio等许多程序可以检测到外部程序修改文件,并在用户选择时重新加载文件。在C++中是否有一种相对简单的方法来执行此类操作(不一定需要跨平台)?

6个回答

133

根据平台的不同,可以采用多种方法来实现此功能。以下是我会从以下选项中进行选择:

跨平台

Trolltech's Qt有一个名为 QFileSystemWatcher 的对象,可允许您监视文件和目录。我相信还有其他跨平台框架也能提供这种功能,但在我使用经验中,这个框架效果还不错。

Windows (Win32)

有一个名为FindFirstChangeNotification的Win32 API可以完成此任务。有一篇很好的文章介绍了该API的一个小封装类:如何获得指定目录发生更改的通知,可以帮助您入门。

Windows (.NET Framework)

如果您可以使用.NET框架的C++ / CLI,则System.IO.FileSystemWatcher就是您的首选类。微软有一篇很好的文章介绍如何使用这个类监视文件系统更改

OS X

FSEvents API是适用于OS X 10.5及以上版本的,具有非常完整的功能。

Linux

如Alex在他的答案中提到的,可以使用inotify来实现此功能。


3
注意:inotify 是特定于 Linux 的,如果您想要一些可移植的 UNIX 特性,可能要寻找类似于 libfam 的东西。 - Artyom
1
FileSystemWatcher在Windows 8中存在问题(不会修复)https://connect.microsoft.com/VisualStudio/feedback/details/772182/msdn-forum-why-are-filesystemwatcher-attribute-changes-detected-on-windows-7-but-not-windows-8 - Amir
FSEvents文档中提到:“文件系统事件API并不适用于查找特定文件何时发生更改。对于这种情况,kqueues机制更为合适。” - Dan
1
FSEvents 在 macOS 上的链接已经失效。 - TChapman500
云盘在WebDAV协议中提供了这样的功能。 - BREMI
显示剩余4条评论

22

2
好的回答!这确实是一个操作系统级别的任务,很难实现跨平台。 - Justin Ethier

13

SimpleFileWatcher可能是您正在寻找的东西。但当然,它是一个外部依赖项 - 也许对您来说这不是一个选项。


1
超级简单且轻量级的解决方案。谢谢。 - Pythagoras of Samos
@MartinGerhardy 的 Github 链接已经失效。 - Michael
很棒的库!如果你直接将文件包含到项目中,它就不再是一个依赖项,而只是帮助文件...这就是我所做的,效果非常好! - poukill
注意:这个库有一个bug。如果你删除一个包含文件的子文件夹,当你删除该文件夹时,文件删除事件不会被触发(在Windows中)。也许为每个子文件夹添加监听器(你可以从文件添加事件中检测到新文件夹)可以解决这个问题。 - klenium
我最近将我的IO处理程序切换到libuv——它还实现了文件/目录监视器支持,而且跨平台。 - Martin Gerhardy

6
一个WinCE的工作示例
void FileInfoHelper::WatchFileChanges( TCHAR *ptcFileBaseDir, TCHAR *ptcFileName ){
static int iCount = 0;
DWORD dwWaitStatus; 
HANDLE dwChangeHandles; 

if( ! ptcFileBaseDir || ! ptcFileName ) return;

wstring wszFileNameToWatch = ptcFileName;

dwChangeHandles = FindFirstChangeNotification(
    ptcFileBaseDir,
    FALSE,
    FILE_NOTIFY_CHANGE_FILE_NAME |
    FILE_NOTIFY_CHANGE_DIR_NAME |
    FILE_NOTIFY_CHANGE_ATTRIBUTES |
    FILE_NOTIFY_CHANGE_SIZE |
    FILE_NOTIFY_CHANGE_LAST_WRITE |
    FILE_NOTIFY_CHANGE_LAST_ACCESS |
    FILE_NOTIFY_CHANGE_CREATION |
    FILE_NOTIFY_CHANGE_SECURITY |
    FILE_NOTIFY_CHANGE_CEGETINFO
    );

if (dwChangeHandles == INVALID_HANDLE_VALUE) 
{
    printf("\n ERROR: FindFirstChangeNotification function failed [%d].\n", GetLastError());
    return;
}

while (TRUE) 
{ 
    // Wait for notification.
    printf("\n\n[%d] Waiting for notification...\n", iCount);
    iCount++;

    dwWaitStatus = WaitForSingleObject(dwChangeHandles, INFINITE); 
    switch (dwWaitStatus) 
    { 
        case WAIT_OBJECT_0: 

            printf( "Change detected\n" );

            DWORD iBytesReturned, iBytesAvaible;
            if( CeGetFileNotificationInfo( dwChangeHandles, 0, NULL, 0, &iBytesReturned, &iBytesAvaible) != 0 ) 
            {
                std::vector< BYTE > vecBuffer( iBytesAvaible );

                if( CeGetFileNotificationInfo( dwChangeHandles, 0, &vecBuffer.front(), vecBuffer.size(), &iBytesReturned, &iBytesAvaible) != 0 ) {
                    BYTE* p_bCurrent = &vecBuffer.front();
                    PFILE_NOTIFY_INFORMATION info = NULL;

                    do {
                        info = reinterpret_cast<PFILE_NOTIFY_INFORMATION>( p_bCurrent );
                        p_bCurrent += info->NextEntryOffset;

                        if( wszFileNameToWatch.compare( info->FileName ) == 0 )
                        {
                            wcout << "\n\t[" << info->FileName << "]: 0x" << ::hex << info->Action;

                            switch(info->Action) {
                                case FILE_ACTION_ADDED:
                                    break;
                                case FILE_ACTION_MODIFIED:
                                    break;
                                case FILE_ACTION_REMOVED:
                                    break;
                                case FILE_ACTION_RENAMED_NEW_NAME:
                                    break;
                                case FILE_ACTION_RENAMED_OLD_NAME:
                                    break;
                            }
                        }
                    }while (info->NextEntryOffset != 0);
                }
            }

            if ( FindNextChangeNotification( dwChangeHandles ) == FALSE )
            {
                printf("\n ERROR: FindNextChangeNotification function failed [%d].\n", GetLastError());
                return;
            }

            break; 

        case WAIT_TIMEOUT:
            printf("\nNo changes in the timeout period.\n");
            break;

        default: 
            printf("\n ERROR: Unhandled dwWaitStatus [%d].\n", GetLastError());
            return;
            break;
    }
}

FindCloseChangeNotification( dwChangeHandles );
}

6
当然,就像VC++一样。当你打开文件时,你会得到最后修改时间,并在你打开文件时定期检查它。如果最后修改时间大于保存的修改时间,那么就发生了改变。

12
民意调查是一种效率很低的做法。正如Alex所指出的,Windows有可用的通知功能(当然我不知道VS是否使用它们)。 - Matthew Flaschen
19
“轮询是一种非常低效的方法。”这是无稽之谈。每5分钟进行一次stat(2)调用对效率几乎没有影响。当您使用“低效”这个词时,需要量化所需的时间或成本,并将其与寻找“高效”解决方案的时间进行比较。如果像在这种情况下一样,差异约为1e6,那么您可能正在进行一种错误的优化。 - Charlie Martin
9
针对检查单个文件(就像原始问题提到的那样),轮询机制已经足够快速。但如果想对深度不受限制的目录中的任何更改进行操作,轮询机制可能很快难以应付。 - Javier
8
每个/file/只能进行一次统计检查。如果您想监视目录树中数百个文件(对于开发复杂项目的开发人员来说并不奇怪),该怎么办?至于寻找解决方案的时间,我大约花了10秒钟找到“Directory Change Notifications”/FindFirstChangeNotification API。因此,我认为这一点既不过早也不荒谬。我也认为当陈述显而易见时,我无需给出确切的成本。 - Matthew Flaschen
2
这种方法的一个变体是只在应用程序获得焦点时轮询。这在用户修改文件的情况下效果很好。我不确定同时进行大量更改注册的成本有多高...而且对其进行分析也不太可能,因为这样的成本是连续的。虽然我怀疑成本并不高。即便如此,轮询也不完全糟糕。 - Brian
显示剩余5条评论

1

为libuv添加一个答案(尽管它是用C编写的),它支持使用特定于系统的API在Windows和Linux上运行:

在Linux上使用inotify,在Darwin上使用FSEvents,在BSD上使用kqueue, 在Windows上使用ReadDirectoryChangesW,在Solaris上使用事件端口,在Cygwin上不支持

您可以在此处查看文档,但请注意,该文档表示与通知相关的API不太一致。


libuv可以监视同一文件系统中的文件移动吗? - Some Name
1
似乎文件移动不是正常的文件系统事件,文档中没有关于“move”事件的任何说明。 - prehistoricpenguin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接