【问题标题】:How do I make my program watch for file modification in C++?如何让我的程序监视 C++ 中的文件修改?
【发布时间】:2010-10-30 04:29:42
【问题描述】:

有很多程序,例如 Visual Studio,可以检测外部程序何时修改文件,然后在用户需要时重新加载文件。有没有一种相对简单的方法可以在 C++ 中做这种事情(不一定必须独立于平台)?

【问题讨论】:

    标签: c++ file-io filesystems monitoring fsevents


    【解决方案1】:

    根据平台的不同,有几种方法可以做到这一点。我会从以下选项中进行选择:

    跨平台

    奇趣科技的 Qt 有一个名为 QFileSystemWatcher 的对象,它允许您监视文件和目录。我确信还有其他跨平台框架也可以为您提供这种功能,但根据我的经验,这个框架运行得相当好。

    Windows (Win32)

    有一个名为FindFirstChangeNotification 的 Win32 api 可以完成这项工作。有一篇不错的文章,其中一个名为 How to get a notification if change occurs in a specified directory 的 api 小包装类将帮助您入门。

    Windows(.NET 框架)

    如果您可以在 .NET Framework 中使用 C++/CLI,那么 System.IO.FileSystemWatcher 是您的首选课程。微软有一篇很好的文章 how to monitor file system changes 使用这个类。

    操作系统

    FSEvents API 是 OS X 10.5 的新 API,功能非常全面。

    Linux

    使用 inotify,正如 Alex 在他的回答中提到的那样。

    【讨论】:

    • 注意:inotify 是特定于 Linux 的,如果您想要一些 UNIX 可移植功能,您可能正在寻找类似 libfam 的东西
    • 我认为您将 C++ 与 C++/CLI 混淆了。相似的名字,不同的语言。除此之外,这是一个彻底而有用的答案。
    • FileSystemWatcher 在 Windows 8 connect.microsoft.com/VisualStudio/feedback/details/772182/… 中存在问题(无法修复)
    • QFileSystemWatcher 对它可以观看的文件数量有限制。doc.qt.io/qt-4.8/qfilesystemwatcher.html
    • FSEvents 文档说“文件系统事件 API 也不是为找出特定文件何时更改而设计的。为此,kqueues 机制更合适。”
    【解决方案2】:

    如果您不需要独立于平台,那么在 Linux 上可能比“轮询”(定期检查)更少的机器负载的方法是 inotify,请参阅 http://en.wikipedia.org/wiki/Inotify 以及其中的许多链接例如。对于 Windows,请参阅http://msdn.microsoft.com/en-us/library/aa365261(VS.85).aspx

    【讨论】:

    • 好答案!这确实是一个操作系统级别的任务,很难跨平台。
    【解决方案3】:

    SimpleFileWatcher 可能是您正在寻找的。但当然,它是一种外部依赖——也许这不是你的选择。

    【讨论】:

    • 超级简单轻量级的解决方案。谢谢。
    • @MartinGerhardy Github 链接已损坏
    • 很棒的图书馆!如果您将文件直接包含到您的项目中,它不再是依赖项,而只是帮助文件......这就是我所做的,它很震撼!
    • 注意:这个库有一个错误。如果您删除包含文件的子文件夹,当您删除该文件夹时,将不会触发已删除的文件 evetn(在 Windows 中)。也许为每个子文件夹添加一个监听器(您可以从文件添加事件中检测到一个新文件夹)可以解决它。
    • 我最近将我的 io 处理切换到 libuv - 它还实现了文件/目录观察器支持并且是跨平台的。
    【解决方案4】:

    当然,就像 VC++ 一样。当您打开文件时,您会获得上次修改时间,并在打开文件时定期检查它。如果 last_mod_time > saved_mod_time,它发生了。

    【讨论】:

    • 轮询是一种非常低效的方法。正如 Alex 所说,Windows 确实有可用的通知(当然我不知道 VS 是否使用它们)。
    • @Matthew “轮询是一种非常低效的方法。”废话。每 5 分钟调用一次 stat(2) 会产生 epsilon 影响。当您使用“低效”一词时,量化它的时间或成本,并将其与您花在寻找“高效”解决方案上的时间进行比较。如果在这种情况下,差异在 1e6 的数量级上,那么您可能进行了不正当的优化。
    • 用于检查单个文件(如原始问题所述),轮询足够快。如果您想对无限深度目录的任何更改采取行动,它很快就会失控。
    • 每个 /file/ 进行一次统计检查。如果您想监视目录树中的数百个文件(对于从事复杂项目的开发人员来说完全不合理)怎么办?至于寻找解决方案的时间,我花了大约 10 秒才找到“目录更改通知”/FindFirstChangeNotification API。所以我不认为这是不成熟或不正当的。我也不认为在说明显而易见的情况时我需要给出确切的成本。
    • 对此的一种可能的变体是仅在应用程序获得焦点时进行轮询。在文件仅由用户修改的情况下,这可以正常工作。我不确定同时进行大量变更注册的成本是多少......并且分析它实际上是不可能的,因为这些成本是连续的。不过,我怀疑它的成本很高。即便如此,民意调查也不是很糟糕。
    【解决方案5】:

    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】:

      为 libuv 添加一个答案(尽管它是用 C 编写的),它通过系统特定的 API 支持 Windows 和 Linux:

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

      您可以查看文档here,注意文档说通知相关的API不是很一致。

      【讨论】:

      • libuv 可以监视同一个文件系统中的文件移动吗?
      • 文件移动似乎不是一个正常的文件系统事件,文档没有显示任何关于move事件的信息。
      猜你喜欢
      • 1970-01-01
      • 2017-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-25
      • 1970-01-01
      相关资源
      最近更新 更多