Sathish kumar
Sathish kumar

Reputation: 49

How to find whether a file is modified in Windows using C++?

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.

Upvotes: 0

Views: 568

Answers (1)

RbMm
RbMm

Reputation: 33804

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);
    }
}

Upvotes: 4

Related Questions