0

I am trying to find whether a file is modified or not using FirstChangeNotification in Windows using C++. Can I use FileSystemWatcher class to do the same? Can anyone provide me with a solution for both ways if possible? I have searched and found snippets that I'm not able to understand since i am a beginner on these topics.

     #define _WIN32_WINNT 0x0501
     #include <windows.h>
     #include <stdio.h>

     int main(int argc, char argv[])
     {
         DWORD dwWaitStatus;
         HANDLE dwChangeHandles[2];
         LPCWSTR DirName = L"F:\\myproject";
         LPCWSTR DirName1 = L"F:\\";

         dwChangeHandles[0] = FindFirstChangeNotification(
         DirName,FALSE,FILE_NOTIFY_CHANGE_FILE_NAME);

         if (dwChangeHandles[0] == INVALID_HANDLE_VALUE)
             ExitProcess(GetLastError());
         else
             printf("FindFirstChangeNotification() for file change is 
             OK.\n");


         dwChangeHandles[1] = FindFirstChangeNotification(
         DirName1,TRUE,FILE_NOTIFY_CHANGE_DIR_NAME);

         if (dwChangeHandles[1] == INVALID_HANDLE_VALUE)
         {
               printf("Something wrong!\n");
               ExitProcess(GetLastError());
         }
         else
               printf("FindFirstChangeNotification() for directory change is 
                OK.\n");

         if (dwChangeHandles[0] != INVALID_HANDLE_VALUE && 
                               dwChangeHandles[1] != INVALID_HANDLE_VALUE)
         {
              printf("\nI'm monitoring any file deletion/creation in %S 
                     and\n", DirName);
              printf("I'm monitoring any directory deletion/creation in 
                     %S.\n", DirName1);
     }

     while (TRUE)
     {
         dwWaitStatus = WaitForMultipleObjects(2, dwChangeHandles, FALSE, 
          INFINITE);
         switch (dwWaitStatus)
         {
             case 0: 
             if (FindNextChangeNotification(dwChangeHandles[0]) == FALSE)
             {
                 printf("FindNextChangeNotification() not OK\n");
                 ExitProcess(GetLastError());
              }
              else
              printf("File created/deleted in %S.\n", DirName);
              break;

              case 1:

              if (FindNextChangeNotification(dwChangeHandles[1]) == FALSE)
                   ExitProcess(GetLastError());
              else
                   printf("Directory was deleted/created in %S.\n", 
                          DirName1);
              break;

        default:
                 printf("FindNextChangeNotification(): Invalid return 
                           value.\n");
                  ExitProcess(GetLastError());
           }
       }

       if(FindCloseChangeNotification(dwChangeHandles[0]) != 0)
             printf("FindCloseChangeNotification() is OK\n");
       if(FindCloseChangeNotification(dwChangeHandles[1]) != 0)
             printf("FindCloseChangeNotification() is OK\n");
        return 0;
    }

The above code is in C and i have changed it to C++ and executed it.. It works well for file creation and deletion.. but when i use FILE_NOTIFY_CHANGE_LAST_WRITE instead of FILE_NOTIFY_FILE_NAME it prints the statement twice.

iBug
  • 35,554
  • 7
  • 89
  • 134
  • You can do this in python with one line... C++ might be overkill for this task – user1767754 Nov 25 '17 at 05:38
  • can you please help with c++.. as it is a project for me to do.. – Sathish kumar Nov 25 '17 at 05:41
  • 3
    @user1767754 It's a bad practice to suggest another language. – iBug Nov 25 '17 at 05:46
  • @iBug can you help with this.? – Sathish kumar Nov 25 '17 at 05:49
  • @Sathishkumar I'm not quite familiar with WinAPI. – iBug Nov 25 '17 at 06:03
  • @iBug it's ok.. if you find anything that can help with please update me.. – Sathish kumar Nov 25 '17 at 06:08
  • `#define _WIN32_WINNT 0x0501` It sounds like Windows XP, which is almost *ancient* now. – iBug Nov 25 '17 at 06:40
  • its ok @iBug can you provide me with a code for windows 10 – Sathish kumar Nov 25 '17 at 06:51
  • `FileSystemWatcher` will do what you want, but to use it you have to leave C++ and go use c++-cli, basically C++ for .Net, to use .Net functions. – user4581301 Nov 25 '17 at 07:33
  • Possible duplicate of [How do I make my program watch for file modification in C++?](https://stackoverflow.com/questions/931093/how-do-i-make-my-program-watch-for-file-modification-in-c) – user4581301 Nov 25 '17 at 07:35
  • @user4581301 i have gone through those contents in that link and i could not understand the working.. and so i have posted this question.. – Sathish kumar Nov 25 '17 at 08:49
  • 1
    why you use several handles with `FindFirstChangeNotification` instead **one** handle with `ReadDirectoryChangesW` ? and why do this is synchronous loop instead asynchronous callbacks ? – RbMm Nov 25 '17 at 10:50
  • @RbMm can you explain me `ReadDirectoryChangesW` with example code .. – Sathish kumar Nov 25 '17 at 11:52
  • @RbMm suppose, I want monitor several directories.. that is the reason i am going for several handles.. – Sathish kumar Nov 25 '17 at 11:55
  • if you need monitor several different folders - yes, need separate handle use for every folder. however in any case much more better use `ReadDirectoryChangesW` instead of it restricted shell `FindFirstChangeNotification`. when you monitor several folders, yet more suitable use asynchronous, callback based, io, instead synchronous loops. for example can look [this](https://stackoverflow.com/questions/40593500/using-readdirectorychangesw-asynchronously-in-a-loop/40600579#40600579) – RbMm Nov 25 '17 at 12:03
  • @RbMm can provide me the full code so that i can execute and c.. – Sathish kumar Nov 27 '17 at 08:03

1 Answers1

4

most effective way do this - use ReadDirectoryChangesW with asynchronous file handle. we create class, which encapsulate folder handle and all required info (say for example window handle, to where post notification on change). file handle we bind to system iocp via BindIoCompletionCallback. as result every time when some modifications occur - our callback will be automatically called in system thread. here we can direct process changes or say post notify/data to gui thread. etc. after this, if no errors, callback again call ReadDirectoryChangesW and so on. when we finally want stop monitoring - we can simply close file handle. as result current active i/o request will be completed with error ERROR_NOTIFY_CLEANUP or if i/o request not active, when we close handle, we detect this and direct call callback with error code. when callback got error we stop monitoring and release object. with this we can create any count of monitor objects and continue execute another tasks. say gui thread - continue run message loop. for example:

#include <windows.h>

#ifdef __cplusplus
extern "C" {
#endif

NTSYSAPI
ULONG
__cdecl
DbgPrint (
          _In_z_ _Printf_format_string_ PCSTR Format,
          ...
          );

NTSYSAPI
ULONG
NTAPI
RtlNtStatusToDosError (
                       _In_ NTSTATUS Status
                       );

#ifdef __cplusplus
}
#endif

class CMonitor
{
    friend struct NDC_IRP;

    HANDLE _hFile;
    DWORD _dwNotifyFilter;
    LONG _nRef;
    LONG _HandleLock;

    BOOL LockHandle();

    void UnlockHandle();

    ~CMonitor()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
        Close();
    }

    BOOL OnComplete(NDC_IRP* Irp, DWORD dwErrorCode, ULONG dwNumberOfBytesTransfered, PFILE_NOTIFY_INFORMATION pfni );

public:
    CMonitor(DWORD dwNotifyFilter) : _nRef(1), _HandleLock(0), _hFile(0)
    { 
        _dwNotifyFilter = dwNotifyFilter;
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
    }

    void AddRef()
    {
        InterlockedIncrement(&_nRef);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_nRef)) delete this;
    }

    void Assign(HANDLE hFile)
    {
        _hFile = hFile, _HandleLock = 0x80000000;
    }

    void Close();

    ULONG Open(PCWSTR FileName);

    ULONG DoRead();

    ULONG DoRead(NDC_IRP* irp);

    static void DumpDirectoryChanges(PVOID pv);
};

struct NDC_IRP : OVERLAPPED
{
    CMonitor* _pObj;

    union {
        FILE_NOTIFY_INFORMATION _fni;
        BYTE _buf[0x1000];// aligned as FILE_NOTIFY_INFORMATION
    };

    NDC_IRP(CMonitor* pObj) : _pObj(pObj)
    {
        pObj->AddRef();

        RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
    }

    ~NDC_IRP()
    {
        _pObj->Release();
    }

    VOID IOCompletionRoutine(DWORD dwErrorCode, ULONG dwNumberOfBytesTransfered)
    {
        if (_pObj->OnComplete(this, dwErrorCode, dwNumberOfBytesTransfered, &_fni))
        {
            delete this;
        }
    }

    static VOID CALLBACK _IOCompletionRoutine(ULONG status, ULONG dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
    {
        static_cast<NDC_IRP*>(lpOverlapped)->IOCompletionRoutine(RtlNtStatusToDosError(status), dwNumberOfBytesTransfered);
    }

    static ULONG Bind(HANDLE hFile)
    {
        return BindIoCompletionCallback(hFile, _IOCompletionRoutine, 0) ? NOERROR : GetLastError();
    }

    DWORD CheckErrorCode(DWORD dwErrorCode)
    {
        switch (dwErrorCode)
        {
        case NOERROR:
        case ERROR_IO_PENDING:
            break;
        default:
            IOCompletionRoutine(dwErrorCode, 0);
        }

        return ERROR_IO_PENDING;
    }
};

//////////////////////////////////////////////////////////////////////////
// CMonitor

void CMonitor::Close()
{
    if (LockHandle())
    {
        _interlockedbittestandreset(&_HandleLock, 31);
        UnlockHandle();
    }
}

BOOL CMonitor::LockHandle()
{
    LONG Value = _HandleLock, NewValue;

    for ( ; Value < 0; Value = NewValue)
    {
        NewValue = _InterlockedCompareExchange(&_HandleLock, Value + 1, Value);

        if (NewValue == Value) return TRUE;
    }

    return FALSE;
}

void CMonitor::UnlockHandle()
{
    if (!_InterlockedDecrement(&_HandleLock))
    {
        CloseHandle(_hFile);
        _hFile = 0;
    }
}

ULONG CMonitor::DoRead()
{
    if (NDC_IRP* irp = new NDC_IRP(this))
    {
        return DoRead(irp);
    }

    return ERROR_NO_SYSTEM_RESOURCES;
}

ULONG CMonitor::DoRead(NDC_IRP* irp)
{
    ULONG err = ERROR_INVALID_HANDLE;

    if (LockHandle())
    {
        err = ReadDirectoryChangesW(_hFile, 
            irp->_buf, sizeof(irp->_buf), TRUE, _dwNotifyFilter, 0, irp, 0)
            ? ERROR_IO_PENDING : GetLastError();

        UnlockHandle();
    }

    irp->CheckErrorCode(err);

    return NOERROR;
}

ULONG CMonitor::Open(PCWSTR FileName)
{
    HANDLE hFile = CreateFile(FileName, FILE_LIST_DIRECTORY, 
        FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, 0,
        OPEN_EXISTING, FILE_FLAG_OVERLAPPED|FILE_FLAG_BACKUP_SEMANTICS, 0);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        return GetLastError();
    }

    if (ULONG err = NDC_IRP::Bind(hFile))
    {
        return err;
    }

    Assign(hFile);

    return NOERROR;
}

BOOL CMonitor::OnComplete(NDC_IRP* irp, DWORD dwErrorCode, ULONG dwNumberOfBytesTransfered, PFILE_NOTIFY_INFORMATION pfni )
{
    switch (dwErrorCode)
    {
    case ERROR_NOTIFY_CLEANUP:
        DbgPrint("%p> ---- NOTIFY_CLEANUP -----\n", this);
        break;

    case ERROR_NOTIFY_ENUM_DIR:
        DbgPrint("%p> ---- ERROR_NOTIFY_ENUM_DIR -----\n", this);
    case NOERROR:
        if (dwNumberOfBytesTransfered) 
        {
            DumpDirectoryChanges(pfni);
        }
        DoRead(irp);
        return FALSE;// reuse irp

    default:
        DbgPrint("%p> error=%x\n", this, dwErrorCode);
    }

    return TRUE;// free irp
}

void CMonitor::DumpDirectoryChanges(PVOID pv)
{
    union {
        PVOID buf;
        PBYTE pb;
        PFILE_NOTIFY_INFORMATION pfni;
    };

    buf = pv;

    for (;;)
    {
        DbgPrint("%x <%.*S>\n", pfni->Action, pfni->FileNameLength >> 1, pfni->FileName);

        ULONG NextEntryOffset = pfni->NextEntryOffset;

        if (!NextEntryOffset)
        {
            break;
        }

        pb += NextEntryOffset;
    }
}

#define FILE_NOTIFY_VALID_MASK          0x00000fff

void CloseMonitor(CMonitor*p)
{
    p->Close();
    p->Release();
}

ULONG CreateMonitor(CMonitor** ppvObj, PCWSTR FileName, DWORD dwNotifyFilter)
{
    if (CMonitor* p = new CMonitor(dwNotifyFilter))
    {
        ULONG err;

        if (!(err = p->Open(FileName)) && !(err = p->DoRead()))
        {
            *ppvObj = p;

            return NOERROR;
        }

        CloseMonitor(p);

        return err;
    }

    return ERROR_NO_SYSTEM_RESOURCES;
}

void demo()
{
    CMonitor *p, *q;

    if (!CreateMonitor(&p, L"c:\\", FILE_NOTIFY_VALID_MASK))
    {
        if (!CreateMonitor(&q, L"d:\\", FILE_NOTIFY_VALID_MASK))
        {
            MessageBoxW(0, 0, L"monitoring..", MB_ICONINFORMATION);
            CloseMonitor(q);
        }
        CloseMonitor(p);
    }
}
RbMm
  • 31,280
  • 3
  • 35
  • 56