如何使程序监视C ++中的文件修改?


75

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

Answers:


110

取决于平台,有几种方法可以做到这一点。我将从以下选择中进行选择:

跨平台

Trolltech的Qt有一个名为QFileSystemWatcher的对象,它使您可以监视文件和目录。我确信还有其他跨平台框架也可以为您提供这种功能,但是根据我的经验,这个功能相当不错。

Windows(Win32)

有一个名为FindFirstChangeNotification的Win32 API可以完成这项工作。有一篇不错的文章,其中有一个小型的api包装类,称为“如果在指定目录中发生更改,如何获取通知”这将使您入门。

Windows(.NET Framework)

如果可以将C ++ / CLI与.NET Framework一起使用, 则可以选择System.IO.FileSystemWatcher。Microsoft有一篇不错的文章, 介绍如何使用此类监视文件系统更改

OS X

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

的Linux

使用inotify,就像Alex在回答中提到的那样。


3
注意:inotify是特定于Linux的,如果您想要一些UNIX可移植功能,可能正在寻找libfam之类的东西
Artyom

我认为您将C ++与C ++ / CLI混淆了。名称相似,语言不同。除此之外,这是一个彻底而有用的答案。
马修·弗拉申

1
FileSystemWatcher在Windows 8中有问题(不会得到解决)connect.microsoft.com/VisualStudio/feedback/details/772182/…–
Amir

QFileSystemWatcher对其可以观看的文件数量有限制。doc.qt.io/qt-4.8/qfilesystemwatcher.html
Zaid

FSEvents文档说:“文件系统事件API也不旨在发现特定文件何时发生更改。为此,kqueues机制更合适。”
Dan


11

可能正在寻找SimpleFileWatcher。但是,当然这是一个外部依赖关系-也许这对您来说不是选择。


1
超级简单轻巧的解决方案。谢谢。
Patryk Czachurski 2015年

@MartinGerhardy Github链接已断开
Michael

很棒的图书馆!如果您将文件直接包含到您的projet中,那么它不再是依赖项,而只是帮助程序文件...这就是我所做的事情,一切都变的如此!
poukill

注意:这个库有一个错误。如果删除包含文件的子文件夹,则在删除文件夹时,不会触发已删除文件evetn(在Windows中)。也许向每个子文件夹添加一个侦听器(您可以从添加文件事件中检测到新文件夹)可以解决该问题。
klenium

我最近将io处理切换到libuv-它也实现了文件/目录监视程序支持,并且是跨平台的。
Martin Gerhardy

5

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


10
轮询是一种非常低效的方法。正如Alex所述,Windows确实有可用的通知(尽管我当然不知道VS是否使用它们)。
马修·弗拉申

17
@Matthew“轮询是一种非常低效的方法。” 废话。每5分钟调用一次stat(2)会影响ε。当您使用“低效”一词时,请量化其时间或成本,并将其与您寻找“高效”解决方案所花费的时间进行比较。在这种情况下,如果差异约为1e6,则可能是在进行错误的优化。
查理·马丁

9
对于检查单个文件(如原始问题所述),轮询足够快。如果您想对无界深度目录进行任何更改,它可能会很快失去控制。
哈维尔

7
每个/ file /进行一次统计检查。如果您想监视目录树中的数百个文件(对于从事复杂项目的开发人员来说并非完全不合理)?至于寻找解决方案的时间,我花了大约10秒钟才找到“目录更改通知” / FindFirstChangeNotification API。因此,我认为这根本不为时过早或不合常理。我也认为在陈述显而易见的事实时,我不需要付出确切的代价。
马修·弗拉申

2
对此的一种变型是仅在应用程序获得关注时才进行轮询。在仅由用户修改文件的情况下,这可以很好地工作。我不确定同时进行大量变更注册要花多少钱...并且对其进行分析实际上是不可能的,因为此类费用是连续的。我怀疑这会花费很多。即使这样,轮询也不是很糟糕。
布赖恩(Brian)

4

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 );
}

1

添加libuv的答案(尽管它是用C编写的),它具有Windows和Linux以及系统特定的API的支持:

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

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


可以libuv监视同一文件系统中的文件移动吗?
一些名称

1
看来文件移动不是正常的文件系统事件,文档没有显示任何有关该move事件的信息。
史前
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.