Skip to content

Instantly share code, notes, and snippets.

@nickav
Last active October 3, 2024 03:51
Show Gist options
  • Save nickav/a57009d4fcc3b527ed0f5c9cf30618f8 to your computer and use it in GitHub Desktop.
Save nickav/a57009d4fcc3b527ed0f5c9cf30618f8 to your computer and use it in GitHub Desktop.
Example of how to poll ReadDirectoryChangesW on Windows
int main() {
char *path = "/path/to/my/directory";
print("watching %s for changes...\n", path);
HANDLE file = CreateFile(path,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
assert(file != INVALID_HANDLE_VALUE);
OVERLAPPED overlapped;
overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL);
uint8_t change_buf[1024];
BOOL success = ReadDirectoryChangesW(
file, change_buf, 1024, TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_LAST_WRITE,
NULL, &overlapped, NULL);
while (true) {
DWORD result = WaitForSingleObject(overlapped.hEvent, 0);
if (result == WAIT_OBJECT_0) {
DWORD bytes_transferred;
GetOverlappedResult(file, &overlapped, &bytes_transferred, FALSE);
FILE_NOTIFY_INFORMATION *event = (FILE_NOTIFY_INFORMATION*)change_buf;
for (;;) {
DWORD name_len = event->FileNameLength / sizeof(wchar_t);
switch (event->Action) {
case FILE_ACTION_ADDED: {
wprintf(L" Added: %.*s\n", name_len, event->FileName);
} break;
case FILE_ACTION_REMOVED: {
wprintf(L" Removed: %.*s\n", name_len, event->FileName);
} break;
case FILE_ACTION_MODIFIED: {
wprintf(L" Modified: %.*s\n", name_len, event->FileName);
} break;
case FILE_ACTION_RENAMED_OLD_NAME: {
wprintf(L"Renamed from: %.*s\n", name_len, event->FileName);
} break;
case FILE_ACTION_RENAMED_NEW_NAME: {
wprintf(L" to: %.*s\n", name_len, event->FileName);
} break;
default: {
printf("Unknown action!\n");
} break;
}
// Are there more events to handle?
if (event->NextEntryOffset) {
*((uint8_t**)&event) += event->NextEntryOffset;
} else {
break;
}
}
// Queue the next event
BOOL success = ReadDirectoryChangesW(
file, change_buf, 1024, TRUE,
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_LAST_WRITE,
NULL, &overlapped, NULL);
}
// Do other loop stuff here...
}
}
@mikeg-asi
Copy link

Good one

@tuannv1807
Copy link

thanks

@SamWindell
Copy link

SamWindell commented Mar 27, 2024

Something additional to note: the change_buf needs to be DWORD aligned, as stated in the docs: "A pointer to the DWORD-aligned formatted buffer in which the read results are to be returned". Power-of-2 alignments greater than alignof(DWORD) should also work. For example, malloc always returns memory locations that are 16-byte aligned (or a similarly large alignment).

We can use the C++11 alignas specifier for aligning a local buffer: alignas(DWORD) uint8_t change_buf[1024];

Edit: there's an additional alignment problem that should be noted. It seems that Windows will sometimes give you values for NextEntryOffset that if you directly cast FILE_NOTIFY_INFORMATION from the u8 buffer at that point, it will be a misaligned-memory read. This probably will still be valid on your CPU - but with a speed penalty, or it might be a crash. If you have Clang/GCC Undefined Behavior Sanitizer enabled you may get a crash here. The workaround for this problem is to memcpy the data out of the buffer into another FILE_NOTIFY_INFORMATION which you know will be aligned correctly. Be careful with the FileName member while doing this - you probably need to do a separate memcpy for that; offsetof(FILE_NOTIFY_INFORMATION, FileName) may be helpful here to determine the memory location to copy from.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment