Does anyone have a FileSystemWatcher-like class in C++/WinAPI?
Asked Answered
D

5

14

I need a .Net's FileSystemWatcher analog in raw C++/WinAPI. I almost started to code one myself using FindFirstChangeNotification/FindNextChangeNotification, but then it occurred to me that I am probably not the first one who needs this and maybe someone will be willing to share.

Ideally what I need is a class which can be used as follows:

FileWatcher fw;
fw.startWatching("C:\MYDIR", "filename.dat", 
     FileWatcher::SIZE | FileWatcher::LAST_WRITE,
     &myChangeHandler);
...
fw.stopWatching();

Or if it would use somehting like boost::signal it would be even better. But please, no dependencies other than the Standard Library, boost and raw WinAPI. Thanks!

Dunn answered 21/1, 2010 at 6:13 Comment(3)
Does this link help?Head
Very good! But doesn't support watching for a specific file, and requires a window to receive notifications. But still can be used as a blueprint for own implementation.Dunn
The URL relisoft.com/win32/watcher.html is deadBelch
I
8

What about the ReadDirectoryChangesW function?

http://msdn.microsoft.com/en-us/library/aa365465(VS.85).aspx

It stores notifications in a buffer so you don't miss any changes (unless the buffer overflows)

Invective answered 21/1, 2010 at 22:5 Comment(2)
It seems to impose periodical polling strategy, but I'd like to get notified of the changes (I need to reload a file when it is changed).Dunn
Sorry, I was wrong. It allows to wait on an event. Seems to be a viable option.Dunn
S
6

2021 answer:

A forked version of the repo listed below that is actively maintained: https://github.com/SpartanJ/efsw

Old answer:

This is a cross-platform solution, but does the job wrapping the Win32 stuff nicely: https://github.com/jameswynn/simplefilewatcher

Samale answered 17/9, 2013 at 8:47 Comment(1)
A cross-platform solution seems nicer than a windows-specific solution.Blankbook
C
5

There is some public-domain code here. My current project uses this (inherited from previous developers). It works pretty well but we do miss notifications for reasons that are unclear (and possibly not caused by this code).

Note that the Win32 API here has some limitations which make it difficult/impossible to avoid missing notifications. Background and alleged work-round for the API are here

Chamness answered 21/1, 2010 at 19:56 Comment(4)
Thanks, seems to be a very nice piece of code! Very, very close indeed. Unfortunately depends on MFC (CString, CSingleLock, CCriticalSection) thus can't be used as is and needs rewriting.Dunn
Yes, and in fact our own usage of this code is not MFC-dependent. However, the hard part here is the signal management from the Win32 API and this seems to work well.Chamness
Still needs too much rewriting to get rid of MFC. I may be wrong, but I feel that carefully wrapping ReadDirectoryChangesW myself would be both faster and easier.Dunn
Strange behavior of the notification API can be seen for things like printer events as well: https://mcmap.net/q/829751/-findnextprinterchangenotification-returns-null-for-ppprinternotifyinfo #18595673Akerboom
C
1

http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.created%28v=vs.71%29.aspx the above does throgh C#, we can always write a COM Wrapper

Cubicle answered 30/12, 2011 at 6:52 Comment(0)
S
1

This is an example of ReadDirectoryChangesW, written in go

kernel32dll := w32.NewKernel32DLL()

dirPath := "C://test_dir"
// Get the HANDLE of the target directory
hDir, _ := kernel32dll.CreateFile(dirPath,
    w32.FILE_LIST_DIRECTORY,
    w32.FILE_SHARE_READ|w32.FILE_SHARE_WRITE|w32.FILE_SHARE_DELETE,
    0,
    w32.OPEN_EXISTING,
    w32.FILE_FLAG_BACKUP_SEMANTICS|w32.FILE_FLAG_OVERLAPPED,
    0,
)
defer kernel32dll.CloseHandle(hDir) // close the handle when the program exit

var maxBufferSize uint32 = 96 // depend on you.
buffer := make([]uint8, maxBufferSize)

// a function for reset the data.
memset := func(a []uint8, v uint8) {
    for i := range a {
        a[i] = v
    }
}

// a function for get the filename
getName := func(offset, fileNameLength uint32) string {
    size := fileNameLength / 2
    filename := make([]uint16, size)
    var i uint32 = 0
    for i = 0; i < size; i++ {
        filename[i] = binary.LittleEndian.Uint16([]byte{buffer[offset+2*i], buffer[offset+2*i+1]})
    }
    return syscall.UTF16ToString(filename)
}

var record w32.FILE_NOTIFY_INFORMATION
for {
    var dwBytes uint32 = 0
    memset(buffer, 0) // clear the buffer for use again.

    kernel32dll.ReadDirectoryChanges(hDir,
        uintptr(unsafe.Pointer(&buffer[0])),
        maxBufferSize,
        true, // bWatchSubtree
        w32.FILE_NOTIFY_CHANGE_LAST_WRITE|w32.FILE_NOTIFY_CHANGE_CREATION|w32.FILE_NOTIFY_CHANGE_FILE_NAME,
        &dwBytes,
        nil,
        0,
    )

    if dwBytes == 0 { // if successful dwBytes is the number bytes used, or zero for Failed.
        fmt.Printf("Buffer overflow! max-size:%d\n", maxBufferSize)
        return
    }

    record = *(*w32.FILE_NOTIFY_INFORMATION)(unsafe.Pointer(&buffer[0]))
    // There may be many FILE_NOTIFY_INFORMATION. For example, if you rename the file, it will trigger the FILE_ACTION_RENAMED_OLD_NAME and FILE_ACTION_RENAMED_NEW_NAM
    var offsetFilename uint32 = 12 // The 12 is calculated from FILE_NOTIFY_INFORMATION.{NextEntryOffset, Action, FileName Length} => they are uint32 => 4*3=12
    for {
        switch record.Action {
        case w32.FILE_ACTION_ADDED:
            fmt.Println("FILE_ACTION_ADDED")
        case w32.FILE_ACTION_REMOVED:
            fmt.Println("FILE_ACTION_REMOVED")
            return
        case w32.FILE_ACTION_MODIFIED:
            fmt.Println("FILE_ACTION_MODIFIED")
        case w32.FILE_ACTION_RENAMED_OLD_NAME:
            fmt.Println("FILE_ACTION_RENAMED_OLD_NAME")
        case w32.FILE_ACTION_RENAMED_NEW_NAME:
            fmt.Println("FILE_ACTION_RENAMED_NEW_NAME")
        default:
            break
        }

        fmt.Println(getName(offsetFilename, record.FileNameLength))

        if record.NextEntryOffset == 0 {
            break
        }
        offsetFilename = record.NextEntryOffset + 12
        record = *(*w32.FILE_NOTIFY_INFORMATION)(unsafe.Pointer(uintptr(unsafe.Pointer(&buffer[0])) + uintptr(record.NextEntryOffset)))
    }
}

You can go here to get the complete code.

https://github.com/CarsonSlovoka/go-pkg/blob/cf4a28372b05458d715ab118d4ce888b2727ac4d/v2/w32/kernel32_func_test.go#L465-L597

Spancel answered 9/11, 2022 at 2:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.