87

There are a lot of programs, Visual Studio for instance, that can detect when an outside program modifies a file and then reload the file if the user wants chooses. Is there a relatively easy way to do this sort of thing in C++ (doesn't necessarily have to be platform independent)?

Quinn Taylor
  • 44,553
  • 16
  • 113
  • 131
Alex
  • 14,973
  • 13
  • 59
  • 94

6 Answers6

131

There are several ways to do this depending on the platform. I would choose from the following choices:

Cross Platform

Trolltech's Qt has an object called QFileSystemWatcher which allows you to monitor files and directories. I'm sure there are other cross platform frameworks that give you this sort of capability too, but this one works fairly well in my experience.

Windows (Win32)

There is a Win32 api called FindFirstChangeNotification which does the job. There is a nice article which a small wrapper class for the api called How to get a notification if change occurs in a specified directory which will get you started.

Windows (.NET Framework)

If you are ok using C++/CLI with the .NET Framework then System.IO.FileSystemWatcher is your class of choice. Microsoft has a nice article on how to monitor file system changes using this class.

OS X

The FSEvents API is new for OS X 10.5 and very full-featured.

Linux

Use inotify as Alex mentioned in his answer.

Nick Haddad
  • 8,767
  • 3
  • 34
  • 38
  • 3
    Note: inotify is Linux specific, if you want some UNIX portable features you are pobably looking for something like libfam – Artyom May 31 '09 at 04:24
  • I think you're confusing C++ with C++/CLI. Similar name, different language. Other than that, this is a thorough and useful answer. – Matthew Flaschen May 31 '09 at 04:41
  • 1
    FileSystemWatcher has issues (that wont be fixed) in 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 Aug 13 '13 at 19:40
  • QFileSystemWatcher has limitations to the amount of files it can watch.http://doc.qt.io/qt-4.8/qfilesystemwatcher.html – Zaid Oct 31 '16 at 10:34
  • The FSEvents docs say "The file system events API is also not designed for finding out when a particular file changes. For such purposes, the kqueues mechanism is more appropriate." – Dan Apr 13 '17 at 11:48
  • 1
    The `FSEvents` link for macOS is dead. – TChapman500 May 14 '18 at 21:33
  • Inotify does not give PID of whatever modified or did stuff to the file/dir in question. This is a major disadvantage if being used for a monitor for security purposes. – rassa45 Jul 09 '18 at 18:32
  • Another cross-platform solution: [efsw](https://github.com/SpartanJ/efsw) – rustyx Jun 26 '20 at 15:28
  • Cloud drives provides such feature in the WebDAV protocol. – BREMI Nov 01 '21 at 14:11
22

If you don't need to be platform-independent, an approach on Linux that may be less of a machine load than "polling" (checking periodically) is inotify, see http://en.wikipedia.org/wiki/Inotify and the many links from it for example. For Windows, see http://msdn.microsoft.com/en-us/library/aa365261(VS.85).aspx .

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
13

SimpleFileWatcher might be what you are looking for. But of course it is an external dependency - maybe that is no option for you.

Martin Gerhardy
  • 1,860
  • 22
  • 13
  • 1
    Super easy and lightweight solution. Thank you. – Pythagoras of Samos Oct 18 '15 at 17:33
  • @MartinGerhardy Github link is broken – Michael Apr 20 '17 at 00:24
  • Great library! If you include files directly into your projet, it is no longer a dependency, but just helper files... This is what I have done and it rocks ! – poukill May 18 '17 at 14:43
  • Note: this lib has a bug. If you delete a subfolder which has a file, when you delete the folder, the file deleted evetn won't be fired (in Windows). Maybe adding a listener to every subfolders (you can detect a new folder from file added event) can solve it. – klenium Aug 07 '17 at 20:25
  • I recently switch my io handling to libuv - it also has file/dir watcher support implemented and is cross platform. – Martin Gerhardy Jun 26 '20 at 09:00
6

A working exemple for 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 );
}
Ataginsky
  • 61
  • 1
  • 4
6

Sure, just like VC++ does. You get the last modified time when you open the file, and you periodically check it while you have the file open. If last_mod_time > saved_mod_time, it happened.

Charlie Martin
  • 110,348
  • 25
  • 193
  • 263
  • 12
    Polling is a very inefficient way to do this. As Alex noted, Windows does have notifications available (though of course I don't know if VS uses them). – Matthew Flaschen May 31 '09 at 02:18
  • 18
    @Matthew "Polling is a very inefficient way to do this." Nonsense. One stat(2) call every 5 minutes has epsilon impact. When you use the word "inefficient", quantify the time or cost it is, and compare that with the time you spend looking for "efficient" solutions. If, as in this case, the difference is on the order of 1e6, you're probably making a perverse optimization. – Charlie Martin May 31 '09 at 02:22
  • 9
    for checking a single file (as the original question mentions), polling is quick enough. if you want to act on any change on an unbounded-depth directory, it can quickly get out of hand. – Javier May 31 '09 at 02:36
  • 8
    One stat check per /file/. What if you want to monitor hundreds of files (not at all unreasonable for a dev working on a complex project) in a directory tree? As for time looking for the solution, it took me about 10 seconds to find the "Directory Change Notifications"/FindFirstChangeNotification API. So I don't think this is premature or perverse at all. I also don't think I need to give an exact cost when stating the obvious. – Matthew Flaschen May 31 '09 at 04:43
  • 2
    One variation on this that is possible is to only poll when the application gains focus. This works fine in the case that the file is only modified by the user. I'm not sure how much it costs to have a lot of simultaneous change registrations...and profiling it is not really possible, since such costs are continuous. I doubt it costs much, though. Even so, polling is not totally awful. – Brian May 31 '09 at 05:02
  • Gee, @Matthew, I wonder if anyone ever had to solve that problem before? But, real quick now, if a stat call takes 10 msec/file, amd you do it periodically, how many open files do you need to have before it consumes significant time. (Recall that a *closed* file never matters.) I get ROM 1000 files to consume 3 pct of the time. So, as to your other point, you're now concerned about optimizing a few tens of milliseconds for a wild edge case — even as wild an EMACS used as I am, I rarely have 50 files open, much less 1000. So yeah, I think a moment's estimation would have served you well. – Charlie Martin May 31 '09 at 06:45
  • In this particular case I'm doing this for a game (if you update the file that defines a stage for example, I want the stage to reload). Since even a frame of lag can be annoying I think I'll try to avoid polling (though I haven't had a chance to test either method yet, I'm going to default to the one that is likely faster). Thank you for the responses though. – Alex Jun 01 '09 at 06:50
  • 1
    @CharlieMartin: I am inclined to agree with you. While your answer may or may not be good, if anyone says anything about its efficiency (whether for or against) he/she must *always* provide statistics to back his/her statement. Only then can an understanding be achieved... Otw it's just talk. – displayName Nov 02 '15 at 21:10
  • 1
    @Alex But how long will the stage reload actually take? If you poll much more quickly than that the user won't care, also no point polling for levels other than the current level, and perhaps connected levels if you want them updated before they move to them. Of course, a level may be built up from many asset files, and scanning them all might be an issue. If you are also writing the level builder, could you have some IPC that the game app can be notified the builder has saved? Or a key log file that is updated when any of the assets are saved, so you only need to check that one file?. – Gem Taylor Aug 15 '18 at 15:45
  • 1
    I need to check if a file changed once every 6 hours. Polling is the best solution for this. – sigod May 03 '19 at 11:17
1

Add an answer for libuv (though it's written in C), it has support for both Windows and Linux with system-specific APIs:

inotify on Linux, FSEvents on Darwin, kqueue on BSDs, ReadDirectoryChangesW on Windows, event ports on Solaris, unsupported on Cygwin

You may check the document here, beware that the document says that the notification related APIs are not very consistent.

prehistoricpenguin
  • 6,130
  • 3
  • 25
  • 42