File System Watcher in C++ for windows

Watching a directory for file change is a commonly occurring situations for programmers.
Languages like java or C# has built in classes for file watching.in Java The java.nio.file package provides a file change notification API, called the Watch Service API.C# has FileSystemWatcher class defined in System.IO.

C++ doesn’t have a ready made class available for File System watching ; recently I had written a class for it; here I am sharing it.
This CFileSystemWatcher is for Windows written in VC++ using win32 API.

class diagram is shown below

Source code is shown below

IFileWatcherListener.h

#pragma once
#include<string>
class IFileWatcherListener
{
public:
	virtual void OnFileChange(const std::wstring& path)  = 0;
	virtual void OnFileAdded(const std::wstring& path)   = 0;
	virtual void OnFileRemoved(const std::wstring& path) = 0;
	virtual void OnFileRenamed(const std::wstring& path) = 0;
};

CFileSystemWatcher.h

#ifndef CFILEWATCHER_H
#define CFILEWATCHER_H

#include <string>
#include <windows.h>
#include <Winbase.h>
#include <stdlib.h>
#include <stdio.h>
#include <thread>
#include <tchar.h>
#include "IFileWatcherListener.h"
#include <vector>
#define MAX_DIRS 25
#define MAX_FILES 255
#define MAX_BUFFER 4096

/*!
 *  CFileWatcher class.
 *  Watching a directory passed for file change and notifies if there is a change.
 *
 */
class CFileSystemWatcher
{

public:

	/*!
	 *
	 *
	 * \param logDir
	 */
	CFileSystemWatcher(const std::wstring& logDir);

	/*!
	 *
	 *
	 * \return
	 */
	const std::wstring& GetDir() { return m_sDir; }

	/*!
	 *
	 *
	 * \param listener
	 */
	void AddFileChangeListener(IFileWatcherListener* listener);

	/*!
	 * Starts the Watcher
	 *
	 */
	void Start();

	/*!
	 *
	 * Stops the Watcher
	 */
	void Stop();

	/*!
	 * Destructor for File Wacther
	 *
	 */
	~CFileSystemWatcher();
	/*!
	 *
	 *
	 * \return
	 */
	volatile bool Running() const { return m_bRunning; }
	/*!
	 * Sets the Running Status
	 *
	 * \param val
	 */
	void Running(volatile bool val) { m_bRunning = val; }

	/*!
	 * Funtion called when a file is chnaged in watcher directory
	 *
	 * \param fileNmae
	 */
	void OnFileChange(const std::wstring& fileNmae);

	/*!
	 *
	 * Funtion called when a file is added to watch directory
	 *
	 * \param sFile
	 */
	void OnFileAdded(const std::wstring& sFile);
	/*!
	 * Funtion called when a file is removed from watch directory
	 *
	 * \param sFile
	 */
	void OnFileRemoved(const std::wstring& sFile);
	/*!
	 * Funtion called when a file is renamed from watch directory
	 *
	 * \param sFile
	 */
	void OnFileRenamed(const std::wstring& sFile);

private:
	/*!
	 * Running Status
	 *
	 */
	volatile bool m_bRunning;

	/*!
	 *
	 * File Watcher Directory
	 */
	std::wstring m_sDir;

	/**
	 * List of listeners to be notified.
	 */
	std::vector<IFileWatcherListener*> m_Listeners;

	/*!
	 * File Watcher Thread
	 *
	 */
	std::unique_ptr<std::thread> m_pFileWatcherThread;
};

/*!
 * Internal Thread function executing  win32 file watcher API
 *
 */
void fileWatcherThread(CFileSystemWatcher& watcherObj);

#endif

CFileSystemWatcher.cpp

#include "pch.h"
#include <iostream>
#include <thread>
#include <future> 
#include "CFileSystemWatcher.h"


void fileWatcherThread(CFileSystemWatcher& watcherObj)
{
	HANDLE hDir = CreateFile(watcherObj.GetDir().c_str(), // pointer to the file name
		FILE_LIST_DIRECTORY,                // access (read/write) mode
		FILE_SHARE_READ | FILE_SHARE_WRITE |FILE_SHARE_DELETE,  // share mode
		NULL,                               // security descriptor
		OPEN_EXISTING,                      // how to create
		FILE_FLAG_BACKUP_SEMANTICS,         // file attributes
		NULL                                // file with attributes to copy
	);
	wchar_t filename[MAX_PATH];
	FILE_NOTIFY_INFORMATION Buffer[1024];
	DWORD BytesReturned;
	while (ReadDirectoryChangesW(
		hDir,                                  // handle to directory
		&Buffer,                                    // read results buffer
		sizeof(Buffer),                                // length of buffer
		TRUE,                                 // monitoring option
		FILE_NOTIFY_CHANGE_SECURITY |
		FILE_NOTIFY_CHANGE_CREATION |
		FILE_NOTIFY_CHANGE_LAST_ACCESS |
		FILE_NOTIFY_CHANGE_LAST_WRITE |
		FILE_NOTIFY_CHANGE_SIZE |
		FILE_NOTIFY_CHANGE_ATTRIBUTES |
		FILE_NOTIFY_CHANGE_DIR_NAME |
		FILE_NOTIFY_CHANGE_FILE_NAME,            // filter conditions
		&BytesReturned,              // bytes returned
		NULL,                          // overlapped buffer
		NULL// completion routine
	) && watcherObj.Running())
	{
		int offset = 0;
		FILE_NOTIFY_INFORMATION* pNotify;
		pNotify = (FILE_NOTIFY_INFORMATION*)((char*)Buffer + offset);
		wcscpy(filename, L"");
		
		wcsncpy(filename, pNotify->FileName, pNotify->FileNameLength / 2);
		
		filename[pNotify->FileNameLength / 2] = NULL;
			
		switch (Buffer[0].Action)
		{
		case FILE_ACTION_MODIFIED: 
			watcherObj.OnFileChange(filename);
			break;
		case FILE_ACTION_ADDED:  
			watcherObj.OnFileAdded(filename);
			break;
		case FILE_ACTION_REMOVED:
			watcherObj.OnFileRemoved(filename);
			break;		
		case FILE_ACTION_RENAMED_OLD_NAME: 
			watcherObj.OnFileRenamed(filename);
			break;
		case FILE_ACTION_RENAMED_NEW_NAME: 
			watcherObj.OnFileRenamed(filename);
			break;
		}		
	}

	CloseHandle(hDir);
}

CFileSystemWatcher::CFileSystemWatcher(const std::wstring& logDir):m_sDir(logDir), m_bRunning(false)
{

}

void CFileSystemWatcher::AddFileChangeListener(IFileWatcherListener* listener)
{
	m_Listeners.push_back(listener);
}

void CFileSystemWatcher::OnFileChange(const std::wstring& fileName)
{
	for (auto& listener : m_Listeners) {
		listener->OnFileChange(fileName);
	}
}

void CFileSystemWatcher::OnFileAdded(const std::wstring & sFile)
{
	for (auto& listener : m_Listeners) {
		listener->OnFileAdded(sFile);
	}
}

void CFileSystemWatcher::OnFileRemoved(const std::wstring & sFile)
{
	for (auto& listener : m_Listeners) {
		listener->OnFileRemoved(sFile);
	}
}

void CFileSystemWatcher::OnFileRenamed(const std::wstring & sFile)
{
	for (auto& listener : m_Listeners) {
		listener->OnFileRenamed(sFile);
	}
}

void CFileSystemWatcher::Start()
{
	m_bRunning = true;
	m_pFileWatcherThread = std::unique_ptr<std::thread>(new std::thread(fileWatcherThread, std::ref(*this)));
	m_pFileWatcherThread->detach();
}

void CFileSystemWatcher::Stop()
{
	m_bRunning = false;
}

CFileSystemWatcher::~CFileSystemWatcher()
{
}

Above class using a sample is shown below

// FileWatcherTest.cpp : Defines the entry point for the console application.
//

#include "pch.h"
#include <iostream>
#include <conio.h>
#include "IFileWatcherListener.h"
#include "CFileWatcher.h"

using namespace std;
class FileWatcherListener : public IFileWatcherListener
{
	// Inherited via IFileWatcherListener
	virtual void OnFileChange(const std::wstring & path) override
	{
		cout << "OnFileChange";
	}

	// Inherited via IFileWatcherListener
	virtual void OnFileAdded(const std::wstring & path) override
	{
		cout << "OnFileAdded";
	}

	// Inherited via IFileWatcherListener
	virtual void OnFileRemoved(const std::wstring & path) override
	{
		cout << "OnFileRemoved";
	}

	// Inherited via IFileWatcherListener
	virtual void OnFileRenamed(const std::wstring & path) override
	{
		cout << "OnFileRenamed";
	}

};

int main()
{
	
	/*!
	 * Creates watcher by passing the watch directory
	 * 
	 */
	CFileSystemWatcher watcher(L"D:\\Log");
	/*!
	 *
	 * Creates Linstener
	 */
	IFileWatcherListener* listener = new FileWatcherListener();
	
	//passes the listener to watcher
	watcher.AddFileChangeListener(listener);
	int choice;
	do
	{
		cout << "\n\nMENU";
		cout << "\n1. Start Directory Watcher";
		cout << "\n2. Stop Directory Watcher";
		cout << "\n3. Exit";
		cout << "\n\nEnter your choice 1-3 :";
		cin >> choice;

		switch (choice)
		{
		case 1:
		  {
			if (!watcher.Running())
				watcher.Start();
			else
				cout << "\nWatcher is already started ..";
		  }
			break;
		case 2:		
			watcher.Stop();
			break;		
		case 3: 
			exit(0);
			break;
		default:
			cout << "\nInvalid choice";
		}
	   } while (choice != 3);
	
	   
	   delete listener;

    return 0;
}


Source code can be downloaded from git repo: https://github.com/sadiquealy/FileWatcher

13 thoughts on “File System Watcher in C++ for windows

  1. IFileWatcherListener.h

    class IFileWatcherListener
    {
    public:
    virtual void OnFileChange(const std::string& path) = 0;

    virtual void OnFileAdded(const std::string& path) = 0;

    virtual void OnFileRemoved(const std::string& path) = 0;

    virtual void OnFileRenamed(const std::string& path) = 0;
    };

    I guess that’s what it looks like.

  2. I don’t find the file #include “pch.h”
    And it gives me te folowing error message:

    #ifndef _CXX0X_WARNING_H
    #define _CXX0X_WARNING_H 1

    #ifndef __GXX_EXPERIMENTAL_CXX0X__
    #error This file requires compiler and library support for the \
    ISO C++ 2011 standard. This support is currently experimental, and must be \
    enabled with the -std=c++11 or -std=gnu++11 compiler options.
    #endif

    #endif

  3. The thread doesn’t exit even after I do watcher.Stop(). So every time I call watcher.Start() it creates a new thread with previous one still running in the background stuck waiting at while loop for any file change. How do I fix this ?

  4. For anyone else using this, renames won’t work correctly due to not walking the incoming buffer. There seems to have been an attempt with the offset variable, but it’s unused, and therefor the FILE_ACTION_RENAMED_NEW_NAME isn’t triggered on renames.

    A fix for this can be found here:
    https://forums.codeguru.com/showthread.php?420427-ReadDirectoryChangesW-not-provide-the-renamed-filename-when-renaming-a-newly-added-fi&s=aab37768e2cd2dfd0acd7eaf64f31db7&p=1658723#post1658723

  5. Pingback: Watching multiple directories with ReadDirectoryChangesW – Windows Questions

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s