[C++] Ein einfacher FileWatcher mit der Win32Api

Früher habe ich mich immer gefragt, wie z. B. Dropbox & Co. Änderungen an Dateien feststellen. Irgendwann bin ich dann über „FileWatcher“ gestolpert. FileWatcher? Genau. Ein FileWatcher überwacht ein Verzeichnis und gibt einem Rückmeldung, ob sich in diesem Verzeichnis etwas geändert hat.

Wahrscheinlich gibt es dazu Bibliotheken, die das ganze Kapseln für Crossplatform. In diesem Blogbeitrag möchte ich euch den Weg für Windows zeigen. Wie der Titel bereits sagt – Ein einfacher FileWatcher mit der Win32Api.

FileWatcher registrieren

Mit FindFirstChangeNotificationW wird der FileWatcher für ein Verzeichnis aktiviert:

HANDLE hChangeNotification = FindFirstChangeNotificationW(
  this->edtPfad->Text.c_str(),
  FALSE,
  FILE_NOTIFY_CHANGE_LAST_WRITE
);

Folgende Parameter werden übergeben:

  • Pfad, der überwacht werden soll
  • Angabe, ob der Verzeichnisbaum überwacht werden soll (also ob auch Unterverzeichnisse auf Änderungen überwacht werden sollen)
  • Die Änderung, auf die geachtet werden soll. Hier sind z. B. auch Änderung des Dateinamens, etc. möglich

Als Rückgabewert erhält man einen Handle, mit dem man weiterarbeiten kann. Die komplette Beschreibung zu der Funktion findet ihr hier.

Als nächstes wird noch kurz geprüft, ob alles geklappt hat, ansonsten wird die Funktion verlassen:

// Pruefen, ob der FileWatcher richtig registriert werden konnte
if (hChangeNotification == INVALID_HANDLE_VALUE || hChangeNotification == NULL)
{
	this->meInfo->Lines->Add(L"Fehler beim registrieren des FileWatchers");
	return;
}

Zum Schluss wird mit WaitForSingleObject gewartet, bis das Objekt ausgelöst wird.

	// Warten, bis im Ordner ein Aenderung auftritt
	WaitForSingleObject(hChangeNotification, INFINITE);

Die ganze Funktion sieht in meinem Fall so aus:

void __fastcall TFormFileWatcher::btnOhneThreadClick(TObject *Sender)
{
	std::list<Datei> lstDateienAlt, lstDateienNeu;

	this->meInfo->Lines->Add(L"FileWatcher gestartet ...");

	// Den aktuellen Stand der Dateien lesen
	this->DateienLesen(this->edtPfad->Text, lstDateienAlt);

	// FileWatcher registrieren
	HANDLE hChangeNotification = FindFirstChangeNotificationW(this->edtPfad->Text.c_str(), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE);

	// Pruefen, ob der FileWatcher richtig registriert werden konnte
	if (hChangeNotification == INVALID_HANDLE_VALUE || hChangeNotification == NULL)
	{
		this->meInfo->Lines->Add(L"Fehler beim registrieren des FileWatchers");
		return;
	}

	// Warten, bis im Ordner ein Aenderung auftritt
	WaitForSingleObject(hChangeNotification, INFINITE);

	// Meldung ausgeben
	this->meInfo->Lines->Add(L"Änderung registriert!");

	// Den aktuellen Stand der Dateien lesen
	this->DateienLesen(this->edtPfad->Text, lstDateienNeu);

	// Alte und neue Dateien vergleichen und evtl. gemachte Aenderungen ausgeben
	this->DateienVergleichen(lstDateienAlt, lstDateienNeu);
}
//---------------------------------------------------------------------------

Das Problem: Der Blockende Aufruf

Das Problem an der Sache: WaitForSingleObject wartet, bis das entsprechende Event aufgerufen wird. Solange das nicht der Fall ist, wird der Thread in dem es aufgerufen wird geblockt. Die Lösung ist wie zu erwarten ein Thread. Ein weiterer Pluspunkt des Threads: Mit FindNextChangeNotification kann gleich wieder auf eine Dateiänderung gewartet werden. Hier ein Beispiel mit einem Aufruf über einen Thread:

void __fastcall TFormFileWatcher::btnMitThreadClick(TObject *Sender)
{
	auto pThread = TThread::CreateAnonymousThread([this]() -> void {
		std::list<Datei> lstDateienAlt, lstDateienNeu;
		this->meInfo->Lines->Add(L"FileWatcher gestartet ...");

		// FileWatcher registrieren
		HANDLE hChangeNotification = FindFirstChangeNotificationW(this->edtPfad->Text.c_str(), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE);

		// Pruefen, ob der FileWatcher richtig registriert werden konnte
		if (hChangeNotification == INVALID_HANDLE_VALUE || hChangeNotification == NULL)
		{
			this->meInfo->Lines->Add(L"Fehler beim registrieren des FileWatchers");
			return;
		}

		while(true)
		{
			lstDateienAlt.clear();
			lstDateienNeu.clear();

			// Den aktuellen Stand der Dateien lesen
			this->DateienLesen(this->edtPfad->Text, lstDateienAlt);

			// Warten, bis im Ordner ein Aenderung auftritt
			WaitForSingleObject(hChangeNotification, INFINITE);

			// Den aktuellen Stand der Dateien lesen
			this->DateienLesen(this->edtPfad->Text, lstDateienNeu);

			// Alte und neue Dateien vergleichen und evtl. gemachte Aenderungen ausgeben
			this->DateienVergleichen(lstDateienAlt, lstDateienNeu);

			FindNextChangeNotification(hChangeNotification);
		}
	});

	pThread->Start();
}
//---------------------------------------------------------------------------

Fazit

Mit FindFirstChangeNotificationW und FindNextChangeNotification kann ziemlich einfach auf Dateiänderungen in einem Verzeichnis gelauscht werden. In meinem Testprogramm prüfe ich Testhalber, welche Dateien sich geändert haben, welche neu hinzugekommen sind und welche gelöscht wurden. Um gleich die geänderte Datei zu bekommen gibt es allerdings noch bessere/effizentere Wege, die allerdings auch *etwas* komplizierter sind. Diese werde ich in einem eigenen Beitrag vorstellen.

Das Beispielprojekt könnt ihr euch bei Github herunterladen.

[C++] Ein einfacher FileWatcher mit der Win32Api

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.

Nach oben scrollen