Does anyone have a FileSystemWatcher-like class in C++/WinAPI?
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!
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)
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
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
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
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
精彩评论