Wie kann ich mein Programm in C ++ auf Dateiänderungen überwachen lassen?

73

Es gibt viele Programme, z. B. Visual Studio, die erkennen können, wenn ein externes Programm eine Datei ändert, und die Datei dann neu laden, wenn der Benutzer dies wünscht. Gibt es eine relativ einfache Möglichkeit, dies in C ++ zu tun (muss nicht unbedingt plattformunabhängig sein)?

Alex
quelle

Antworten:

107

Je nach Plattform gibt es verschiedene Möglichkeiten, dies zu tun. Ich würde aus folgenden Optionen wählen:

Plattformübergreifend

Qt von Trolltech verfügt über ein Objekt namens QFileSystemWatcher , mit dem Sie Dateien und Verzeichnisse überwachen können. Ich bin mir sicher, dass es andere plattformübergreifende Frameworks gibt, die Ihnen diese Art von Funktionen bieten, aber dieses funktioniert meiner Erfahrung nach ziemlich gut.

Windows (Win32)

Es gibt eine Win32-API namens FindFirstChangeNotification, die den Job erledigt. Es gibt einen schönen Artikel, den eine kleine Wrapper-Klasse für die API namens Wie man eine Benachrichtigung erhält, wenn Änderungen in einem angegebenen Verzeichnis auftreten, mit denen Sie beginnen können.

Windows (.NET Framework)

Wenn Sie C ++ / CLI mit .NET Framework verwenden können, ist System.IO.FileSystemWatcher die Klasse Ihrer Wahl. Microsoft hat einen schönen Artikel zum Überwachen von Dateisystemänderungen mit dieser Klasse.

OS X.

Die FSEvents- API ist neu für OS X 10.5 und bietet alle Funktionen .

Linux

Verwenden Sie inotify, wie Alex in seiner Antwort erwähnt hat.

Nick Haddad
quelle
3
Hinweis: inotify ist Linux-spezifisch. Wenn Sie einige tragbare UNIX-Funktionen wünschen, suchen Sie wahrscheinlich nach etwas wie libfam
Artyom
Ich denke, Sie verwechseln C ++ mit C ++ / CLI. Gleicher Name, andere Sprache. Davon abgesehen ist dies eine gründliche und nützliche Antwort.
Matthew Flaschen
1
FileSystemWatcher hat Probleme (die nicht behoben werden) in Windows 8 connect.microsoft.com/VisualStudio/feedback/details/772182/…
Amir
QFileSystemWatcher hat Einschränkungen hinsichtlich der Anzahl der Dateien, die angezeigt werden können. doc.qt.io/qt-4.8/qfilesystemwatcher.html
Zaid
In den FSEvents-Dokumenten heißt es: "Die Dateisystem-Ereignis-API ist auch nicht dafür ausgelegt, herauszufinden, wann sich eine bestimmte Datei ändert. Für solche Zwecke ist der kqueues-Mechanismus besser geeignet."
Dan
11

SimpleFileWatcher könnte das sein, wonach Sie suchen. Aber natürlich ist es eine externe Abhängigkeit - vielleicht ist das für Sie keine Option.

Martin Gerhardy
quelle
1
Super einfache und leichte Lösung. Vielen Dank.
Patryk Czachurski
@ MartinGerhardy Github Link ist gebrochen
Michael
Tolle Bibliothek! Wenn Sie Dateien direkt in Ihr Projekt aufnehmen, ist dies keine Abhängigkeit mehr, sondern nur noch Hilfedateien ... Das habe ich getan und es rockt!
Poukill
Hinweis: Diese Bibliothek hat einen Fehler. Wenn Sie einen Unterordner mit einer Datei löschen, wird beim Löschen des Ordners die evetn gelöschte Datei nicht ausgelöst (unter Windows). Möglicherweise kann das Hinzufügen eines Listeners zu jedem Unterordner (Sie können einen neuen Ordner anhand des Ereignisses "Datei hinzugefügt" erkennen) das Problem lösen.
Klenium
Ich habe kürzlich mein Io-Handling auf libuv umgestellt - es hat auch Datei- / Verzeichnis-Watcher-Unterstützung implementiert und ist plattformübergreifend.
Martin Gerhardy
5

Sicher, genau wie VC ++. Sie erhalten die zuletzt geänderte Zeit, wenn Sie die Datei öffnen, und Sie überprüfen sie regelmäßig, während Sie die Datei geöffnet haben. Wenn last_mod_time> saved_mod_time, ist es passiert.

Charlie Martin
quelle
10
Polling ist ein sehr ineffizienter Weg, dies zu tun. Wie Alex bemerkte, sind in Windows Benachrichtigungen verfügbar (obwohl ich natürlich nicht weiß, ob VS diese verwendet).
Matthew Flaschen
17
@Matthew "Polling ist ein sehr ineffizienter Weg, dies zu tun." Unsinn. Ein stat (2) -Aufruf alle 5 Minuten hat eine Epsilon-Auswirkung. Wenn Sie das Wort "ineffizient" verwenden, quantifizieren Sie die Zeit oder die Kosten und vergleichen Sie diese mit der Zeit, die Sie für die Suche nach "effizienten" Lösungen aufwenden. Wenn der Unterschied wie in diesem Fall in der Größenordnung von 1e6 liegt, führen Sie wahrscheinlich eine perverse Optimierung durch.
Charlie Martin
9
Zum Überprüfen einer einzelnen Datei (wie in der ursprünglichen Frage erwähnt) ist das Abrufen schnell genug. Wenn Sie Änderungen an einem Verzeichnis mit unbegrenzter Tiefe vornehmen möchten, kann dies schnell außer Kontrolle geraten.
Javier
7
Eine Statistikprüfung pro / Datei /. Was ist, wenn Sie Hunderte von Dateien (die für einen Entwickler, der an einem komplexen Projekt arbeitet, überhaupt nicht unangemessen sind) in einem Verzeichnisbaum überwachen möchten? Bei der Suche nach der Lösung habe ich ungefähr 10 Sekunden gebraucht, um die API "Directory Change Notifications" / FindFirstChangeNotification zu finden. Ich halte das also nicht für verfrüht oder pervers. Ich glaube auch nicht, dass ich genaue Kosten angeben muss, wenn ich das Offensichtliche sage.
Matthew Flaschen
2
Eine mögliche Variation besteht darin, nur dann abzufragen, wenn die Anwendung den Fokus erhält. Dies funktioniert einwandfrei, wenn die Datei nur vom Benutzer geändert wird. Ich bin mir nicht sicher, wie viel es kostet, viele Änderungsregistrierungen gleichzeitig zu haben ... und eine Profilerstellung ist nicht wirklich möglich, da diese Kosten kontinuierlich sind. Ich bezweifle jedoch, dass es viel kostet. Trotzdem ist die Umfrage nicht ganz schrecklich.
Brian
4

Ein funktionierendes Beispiel für 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
quelle
1

Fügen Sie eine Antwort für libuv hinzu (obwohl es in C geschrieben ist), es unterstützt sowohl Windows als auch Linux mit systemspezifischen APIs:

Inotify unter Linux, FSEvents unter Darwin, Kqueue unter BSDs, ReadDirectoryChangesW unter Windows, Ereignisports unter Solaris, unter Cygwin nicht unterstützt

Sie können das Dokument hier überprüfen . Beachten Sie, dass das Dokument besagt, dass die Benachrichtigungs-APIs nicht sehr konsistent sind.

prähistorisches Pinguin
quelle
Kann mit libuvdemselben Dateisystem auf Dateiverschiebungen achten?
Einige Namen
1
Es scheint, dass das Verschieben von Dateien kein normales Dateisystemereignis ist. Das Dokument zeigt nichts über das moveEreignis an.
Prähistoricpenguin