- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我正在编写一个小应用程序来监视某个目录中是否有新添加的文件。
我想将监控代码放在一个单独的线程中,这样我就可以让主线程空闲下来处理其他事情,并在需要时取消监控线程。
我能够正确地编写所有代码,除了一件事:
我无法正确退出监控线程,因此出现了这篇文章。
我在主线程中向事件对象发出信号,等待线程退出,然后进行清理。
问题出在我对 ReadDirectoryChangesW
的使用上,因为在我注释掉那段代码后一切正常。
一旦事件句柄发出信号,ReadDirectoryChangesW
就会阻塞线程以防止它“捕获”事件并退出。如果我在目录中添加一个新文件,它会“解锁”ReadDirectoryChangesW
,线程会“捕获”该事件并退出。
为了进一步提供帮助,我制作了一个小 MVCE下面,它说明了我到目前为止所说的内容。
#include <iostream>
#include <Windows.h>
#include <map>
struct SThreadParams
{
HANDLE hEvent;
HANDLE hDir;
int processDirectoryChanges(const char *buffer)
{
if (NULL == buffer) return -1;
DWORD offset = 0;
char fileName[MAX_PATH] = "";
FILE_NOTIFY_INFORMATION *fni = NULL;
do
{
fni = (FILE_NOTIFY_INFORMATION*)(&buffer[offset]);
// since we do not use UNICODE,
// we must convert fni->FileName from UNICODE to multibyte
int ret = ::WideCharToMultiByte(CP_ACP, 0, fni->FileName,
fni->FileNameLength / sizeof(WCHAR),
fileName, sizeof(fileName), NULL, NULL);
switch (fni->Action)
{
case FILE_ACTION_ADDED:
{
std::cout << "FILE_ACTION_ADDED " << fileName << std::endl;
}
break;
case FILE_ACTION_REMOVED:
{
std::cout << "FILE_ACTION_REMOVED " << fileName << std::endl;
}
break;
case FILE_ACTION_MODIFIED:
{
std::cout << "FILE_ACTION_MODIFIED " << fileName << std::endl;
}
break;
case FILE_ACTION_RENAMED_OLD_NAME:
{
std::cout << "FILE_ACTION_RENAMED_OLD_NAME " << fileName << std::endl;
}
break;
case FILE_ACTION_RENAMED_NEW_NAME:
{
std::cout << "FILE_ACTION_RENAMED_NEW_NAME " << fileName << std::endl;
}
break;
default:
break;
}
// clear string so we can reuse it
::memset(fileName, '\0', sizeof(fileName));
// advance to next entry
offset += fni->NextEntryOffset;
} while (fni->NextEntryOffset != 0);
return 0;
}
};
DWORD WINAPI thread(LPVOID arg)
{
SThreadParams p = *((SThreadParams *)arg);
OVERLAPPED ovl = { 0 };
DWORD bytesTransferred = 0, error = 0;
char buffer[1024];
if (NULL == (ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL)))
{
std::cout << "CreateEvent error = " << ::GetLastError() << std::endl;
return ::GetLastError();
};
do {
if (::ReadDirectoryChangesW(p.hDir, buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL))
{
if (::GetOverlappedResult(p.hDir, &ovl, &bytesTransferred, TRUE))
{
for (int i = 0; i < 5; ++i) std::cout << '=';
std::cout << std::endl;
if (-1 == p.processDirectoryChanges(buffer))
std::cout << "processDirectoryChanges error = " << std::endl;
}
else
{
bytesTransferred = 0;
std::cout << "GetOverlappedResult error = " << ::GetLastError() << std::endl;
}
if (0 == ::ResetEvent(ovl.hEvent))
{
std::cout << "ResetEvent error = " << ::GetLastError() << std::endl;
::CloseHandle(ovl.hEvent);
return ::GetLastError();
}
}
else
{
// we shall just output the error, and try again...
std::cout << "ReadDirectoryChangesW error = " << ::GetLastError() << std::endl;
}
error = ::WaitForSingleObject(p.hEvent, 2000);
} while (WAIT_TIMEOUT == error);
::CloseHandle(ovl.hEvent);
return 0;
}
int main()
{
SThreadParams s;
s.hDir = ::CreateFile(SOME_DIRECTORY,
FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (INVALID_HANDLE_VALUE == s.hDir)
{
std::cout << "CreateFile error = " << ::GetLastError() << std::endl;
return 1;
}
s.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == s.hEvent)
{
std::cout << "CreateEvent error = " << ::GetLastError() << std::endl;
::CloseHandle(s.hDir);
return 1;
}
HANDLE hThread = ::CreateThread(NULL, 0, thread, (LPVOID)&s, 0, NULL);
if (NULL == hThread)
{
std::cout << "CreateThread error = " << ::GetLastError() << std::endl;
::CloseHandle(s.hDir);
::CloseHandle(s.hEvent);
return 1;
}
std::cout << "press any key to close program..." << std::endl;
std::cin.get();
if (0 == ::CancelIoEx(s.hDir, NULL))
{
std::cout << "CancelIoEx error = " << ::GetLastError() << std::endl;
::CloseHandle(s.hDir);
::CloseHandle(s.hEvent);
return 1;
}
if (0 == ::SetEvent(s.hEvent))
{
std::cout << "SetEvent error = " << ::GetLastError() << std::endl;
::CloseHandle(s.hDir);
::CloseHandle(s.hEvent);
return 1;
}
// wait for thread to exit
DWORD error = ::WaitForSingleObject(hThread, INFINITE);
std::cout << "Thread exited with error code = " << error << std::endl;
::CloseHandle(s.hEvent);
::CloseHandle(s.hDir);
::CloseHandle(hThread);
return 0;
}
我已将线程中的 OVERLAPPED
结构移出到传递给线程的结构中。然后我将 OVERLAPPED.hEvent
设置为强制“解锁”ReadDirectoryChangesW
。这似乎可行,但让我感到害怕,因为我认为它不安全/容易出错,因为它没有记录。
我曾尝试使用完成例程,但没有成功,因为我是新手。我能够收到通知,但缓冲区的内容(充满 ReadDirectoryChangesW
的缓冲区)在第一次通过后没有被正确读取。我仍在尝试自己完成这项工作,但需要帮助。
我可以使用 I/O 完成端口,但由于我将只监视一个目录,所以我认为这有点矫枉过正。如果我弄错了,请指导我如何为我的案例使用 I/O 完成端口,我很乐意尝试一下。
鉴于上面的 MVCE,你能指导我如何修改线程过程中的代码,以便它正确退出(没有 ReadDirectoryChangesW
阻塞)。
我觉得我将不得不使用完成例程。在那种情况下,我会谦虚地要求一些伪代码或书面说明,因为这是我第一次使用它们。
每当我取得进展时,我都会相应地用相关数据更新这篇文章。
最佳答案
存在 3 种对文件进行异步操作的方法:
您选择了最差的变体。我会在你的地方使用 IoCompletionPort。在这种情况下,您不需要创建事件、线程、调用 GetOverlappedResult
,也不需要任何循环..
所有需要电话BindIoCompletionCallback
(或 RtlSetIoCompletionCallback
)文件和所有!
关于取消 - CancelIoEx
在 XP 中不存在(“我试图支持 Windows XP”),但您可以简单地关闭目录句柄 - 在这种情况下,IO 将被 STATUS_NOTIFY_CLEANUP
取消.所以代码看起来像这样:
RUNDOWN_REF_EVENT g_rundown; // Run-Down Protection
class SPYDATA :
#ifdef _USE_NT_VERSION_
IO_STATUS_BLOCK
#else
OVERLAPPED
#endif
{
HANDLE _hFile;
LONG _dwRef;
union {
FILE_NOTIFY_INFORMATION _fni;
UCHAR _buf[PAGE_SIZE];
};
void DumpDirectoryChanges()
{
union {
PVOID buf;
PBYTE pb;
PFILE_NOTIFY_INFORMATION pfni;
};
buf = _buf;
for (;;)
{
DbgPrint("%x <%.*S>\n", pfni->Action, pfni->FileNameLength >> 1, pfni->FileName);
ULONG NextEntryOffset = pfni->NextEntryOffset;
if (!NextEntryOffset)
{
break;
}
pb += NextEntryOffset;
}
}
#ifdef _USE_NT_VERSION_
static VOID WINAPI _OvCompRoutine(
_In_ NTSTATUS dwErrorCode,
_In_ ULONG_PTR dwNumberOfBytesTransfered,
_Inout_ PIO_STATUS_BLOCK Iosb
)
{
static_cast<SPYDATA*>(Iosb)->OvCompRoutine(dwErrorCode, (ULONG)dwNumberOfBytesTransfered);
}
#else
static VOID WINAPI _OvCompRoutine(
_In_ DWORD dwErrorCode, // really this is NTSTATUS
_In_ DWORD dwNumberOfBytesTransfered,
_Inout_ LPOVERLAPPED lpOverlapped
)
{
static_cast<SPYDATA*>(lpOverlapped)->OvCompRoutine(dwErrorCode, dwNumberOfBytesTransfered);
}
#endif
VOID OvCompRoutine(NTSTATUS status, DWORD dwNumberOfBytesTransfered)
{
DbgPrint("[%x,%x]\n", status, dwNumberOfBytesTransfered);
if (0 <= status)
{
if (status != STATUS_NOTIFY_CLEANUP)
{
if (dwNumberOfBytesTransfered) DumpDirectoryChanges();
DoRead();
}
else
{
DbgPrint("\n---- NOTIFY_CLEANUP -----\n");
}
}
Release();
g_rundown.ReleaseRundownProtection();
}
~SPYDATA()
{
Cancel();
}
public:
void DoRead()
{
if (g_rundown.AcquireRundownProtection())
{
AddRef();
#ifdef _USE_NT_VERSION_
NTSTATUS status = ZwNotifyChangeDirectoryFile(_hFile, 0, 0, this, this, &_fni, sizeof(_buf), FILE_NOTIFY_VALID_MASK, TRUE);
if (NT_ERROR(status))
{
OvCompRoutine(status, 0);
}
#else
if (!ReadDirectoryChangesW(_hFile, _buf, sizeof(_buf), TRUE, FILE_NOTIFY_VALID_MASK, (PDWORD)&InternalHigh, this, 0))
{
OvCompRoutine(RtlGetLastNtStatus(), 0);
}
#endif
}
}
SPYDATA()
{
_hFile = 0;// ! not INVALID_HANDLE_VALUE because use ntapi for open file
_dwRef = 1;
#ifndef _USE_NT_VERSION_
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
#endif
}
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef))
{
delete this;
}
}
BOOL Create(POBJECT_ATTRIBUTES poa)
{
IO_STATUS_BLOCK iosb;
NTSTATUS status = ZwOpenFile(&_hFile, FILE_GENERIC_READ, poa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_DIRECTORY_FILE);
if (0 <= status)
{
return
#ifdef _USE_NT_VERSION_
0 <= RtlSetIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#else
BindIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#endif
}
return FALSE;
}
void Cancel()
{
if (HANDLE hFile = InterlockedExchangePointer(&_hFile, 0))
{
NtClose(hFile);
}
}
};
void DemoF()
{
if (g_rundown.Create())
{
STATIC_OBJECT_ATTRIBUTES(oa, "\\systemroot\\tmp");//SOME_DIRECTORY
if (SPYDATA* p = new SPYDATA)
{
if (p->Create(&oa))
{
p->DoRead();
}
MessageBoxW(0, L"wait close program...", L"", MB_OK);
p->Cancel();
p->Release();
}
g_rundown.ReleaseRundownProtection();
g_rundown.WaitForRundown();
}
}
等待所有 IO 完成后我使用 Run-Down Protection .不幸的是,这不是在用户模式下实现的,但你自己并不难实现这个非常有用的功能。我的实现:
class __declspec(novtable) RUNDOWN_REF
{
LONG _LockCount;
protected:
virtual void RundownCompleted() = 0;
public:
RUNDOWN_REF()
{
_LockCount = 1;
}
BOOL AcquireRundownProtection()
{
LONG LockCount = _LockCount, prevLockCount;
do
{
if (!LockCount)
{
return FALSE;
}
LockCount = InterlockedCompareExchange(&_LockCount, LockCount + 1, prevLockCount = LockCount);
} while (LockCount != prevLockCount);
return TRUE;
}
void ReleaseRundownProtection()
{
if (!InterlockedDecrement(&_LockCount))
{
RundownCompleted();
}
}
};
class RUNDOWN_REF_EVENT : public RUNDOWN_REF
{
HANDLE _hEvent;
virtual void RundownCompleted()
{
SetEvent(_hEvent);
}
public:
BOOL Create()
{
return (_hEvent = CreateEvent(0, TRUE, FALSE, 0)) != 0;
}
RUNDOWN_REF_EVENT()
{
_hEvent = 0;
}
~RUNDOWN_REF_EVENT()
{
if (_hEvent) CloseHandle(_hEvent);
}
void WaitForRundown()
{
if (WaitForSingleObject(_hEvent, INFINITE) != WAIT_OBJECT_0) __debugbreak();
}
};
关于c++ - 异步 ReadDirectoryChangesW 调用阻止线程退出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40342925/
为了让我的代码几乎完全用 Jquery 编写,我想用 Jquery 重写 AJAX 调用。 这是从网页到 Tomcat servlet 的调用。 我目前情况的类似代码: var http = new
我想使用 JNI 从 Java 调用 C 函数。在 C 函数中,我想创建一个 JVM 并调用一些 Java 对象。当我尝试创建 JVM 时,JNI_CreateJavaVM 返回 -1。 所以,我想知
环顾四周,我发现从 HTML 调用 Javascript 函数的最佳方法是将函数本身放在 HTML 中,而不是外部 Javascript 文件。所以我一直在网上四处寻找,找到了一些简短的教程,我可以根
我有这个组件: import {Component} from 'angular2/core'; import {UserServices} from '../services/UserService
我正在尝试用 C 实现一个简单的 OpenSSL 客户端/服务器模型,并且对 BIO_* 调用的使用感到好奇,与原始 SSL_* 调用相比,它允许一些不错的功能。 我对此比较陌生,所以我可能会完全错误
我正在处理有关异步调用的难题: 一个 JQuery 函数在用户点击时执行,然后调用一个 php 文件来检查用户输入是否与数据库中已有的信息重叠。如果是这样,则应提示用户确认是否要继续或取消,如果他单击
我有以下类(class)。 public Task { public static Task getInstance(String taskName) { return new
嘿,我正在构建一个小游戏,我正在通过制作一个数字 vector 来创建关卡,该数字 vector 通过枚举与 1-4 种颜色相关联。问题是循环(在 Simon::loadChallenge 中)我将颜
我有一个java spring boot api(数据接收器),客户端调用它来保存一些数据。一旦我完成了数据的持久化,我想进行另一个 api 调用(应该处理持久化的数据 - 数据聚合器),它应该自行异
首先,这涉及桌面应用程序而不是 ASP .Net 应用程序。 我已经为我的项目添加了一个 Web 引用,并构建了各种数据对象,例如 PayerInfo、Address 和 CreditCard。但问题
我如何告诉 FAKE 编译 .fs文件使用 fsc ? 解释如何传递参数的奖励积分,如 -a和 -target:dll . 编辑:我应该澄清一下,我正在尝试在没有 MSBuild/xbuild/.sl
我使用下划线模板配置了一个简单的主干模型和 View 。两个单独的 API 使用完全相同的配置。 API 1 按预期工作。 要重现该问题,请注释掉 API 1 的 URL,并取消注释 API 2 的
我不确定什么是更好的做法或更现实的做法。我希望从头开始创建目录系统,但不确定最佳方法是什么。 我想我在需要显示信息时使用对象,例如 info.php?id=100。有这样的代码用于显示 Game.cl
from datetime import timedelta class A: def __abs__(self): return -self class B1(A):
我在操作此生命游戏示例代码中的数组时遇到问题。 情况: “生命游戏”是约翰·康威发明的一种细胞自动化技术。它由一个细胞网格组成,这些细胞可以根据数学规则生存/死亡/繁殖。该网格中的活细胞和死细胞通过
如果我像这样调用 read() 来读取文件: unsigned char buf[512]; memset(buf, 0, sizeof(unsigned char) * 512); int fd;
我用 C 编写了一个简单的服务器,并希望调用它的功能与调用其他 C 守护程序的功能相同(例如使用 ./ftpd start 调用它并使用 ./ftpd stop 关闭该实例)。显然我遇到的问题是我不知
在 dos 中,当我粘贴此命令时它会起作用: "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" https://google.
在 dos 中,当我粘贴此命令时它会起作用: "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" https://google.
我希望能够从 cmd 在我的 Windows 10 计算机上调用 python3。 我已重新安装 Python3.7 以确保选择“添加到路径”选项,但仍无法调用 python3 并使 CMD 启动 P
我是一名优秀的程序员,十分优秀!