ReadDirectoryChangesW and GetOverlappedResult

ReadDirectoryChangesW and GetOverlappedResult

本文关键字:GetOverlappedResult and ReadDirectoryChangesW      更新时间:2023-10-16

我正在调用 ReadDirectoryChangesW异步以监视背景线程中的目录更改。

此目录(basePath(打开并启动"读取"线程的方式:

    m_hDIR = CreateFileW(
            basePath,
            FILE_LIST_DIRECTORY | GENERIC_READ,
            FILE_SHARE_WRITE | FILE_SHARE_READ,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
            NULL);
        if (m_hDIR == INVALID_HANDLE_VALUE)
            throw CrException(CrWin32ErrorString());
        //Start reading changes in background thread
        m_Callback = std::move(a_Callback);
        m_Reading = true;
        m_ReadThread = std::thread(&CrDirectoryWatcher::StartRead, this);

这是StartRead() :(注意:m_Readingatomic<bool>(

void StartRead()
        {
            DWORD dwBytes = 0;
            FILE_NOTIFY_INFORMATION fni{0};
            OVERLAPPED o{0};
            //Be sure to set the hEvent member of the OVERLAPPED structure to a unique event.
            o.hEvent = CreateEvent(0, 0, 0, 0);
            while(m_Reading)
            {
                if (!ReadDirectoryChangesW(m_hDIR,
                    &fni, sizeof(fni),
                    TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE,
                    &dwBytes, &o, NULL))
                {
                    CrAssert(0, CrWin32ErrorString());
                }
                if (!GetOverlappedResult(m_hDIR, &o, &dwBytes, FALSE))
                    CrAssert(0, CrWin32ErrorString());
                if (fni.Action != 0)
                {
                    std::wstring fileName(fni.FileName, fni.FileNameLength);
                    m_Callback(fileName);
                    fni.Action = 0;
                }
            }
        }

基本上,我正在"轮询"每个帧的新更改。现在,当我致电GetOverlappedResult()时,它会失败并产生以下错误:

重叠的I/O事件不处于信号状态。

我想念什么吗?ReadDirectoryChangesW是否打算被称为每个" tick"吗?还是在检测到新的更改时?

注意:当我忽略OVERLAPPED struct(和GetOverlappedResult(时,它会起作用,但会阻止线程直到读取更改。这样可以防止我的应用正确终止。(即我不能加入线程(

在调用GetOverlappedResult()时,如果将bWait参数设置为false,并且I/O操作尚未完成,则GetOverlappedResult()使用ERROR_IO_INCOMPLETE错误代码失败:

bwait [in]
如果此参数为TRUE,并且lpOverlapped结构的Internal成员为STATUS_PENDING,则该函数在操作完成后才返回。如果此参数为FALSE,并且该操作仍在待处理,则函数返回FALSEGetLastError功能返回ERROR_IO_INCOMPLETE

这不是致命的错误,所以只忽略该错误并继续。

是的,请确保您不再次致电ReadDirectoryChangesW(),直到GetOverlappedResult()报告了先前的I/O操作已完成。

现在,话虽如此,您的代码还有另一个问题。您的线程正在堆栈上分配一个FILE_NOTIFY_INFORMATION实例。如果您查看FILE_NOTIFY_INFORMATION的定义,则其FileName字段是可变长度:

typedef struct _FILE_NOTIFY_INFORMATION {
  DWORD NextEntryOffset;
  DWORD Action;
  DWORD FileNameLength;
  WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;

文件名
可变长度字段中包含相对于目录句柄的文件名。文件名是Unicode字符格式,未终止。

这意味着在静态上分配 FILE_NOTIFY_INFORMATION会太小,而 dwBytes几乎总是为0,因为 ReadDirectoryChangesW()无法将完整的 FILE_NOTIFY_INFORMATION返回给您(除非 FileName完全是1个字符(:

首次致电ReadDirectoryChangesW时,系统会分配缓冲区以存储更改信息。此缓冲区与目录手柄关联,直到其关闭,并且其尺寸在其一生中不会改变。呼叫此功能之间发生的目录更改将添加到缓冲区中,然后在下一个呼叫中返回。如果缓冲区溢出,则丢弃缓冲区的整个内容,lpBytesReturned参数包含零,并且ReadDirectoryChangesW函数使用错误代码ERROR_NOTIFY_ENUM_DIR

失败。

error_notify_enum_dir
1022(0x3fe(
正在完成通知更改请求,并且该信息未在呼叫者的缓冲区中返回。呼叫者现在需要枚举文件以查找更改

因此,您需要动态分配一个大字节缓冲区以接收FILE_NOTIFY_INFORMATION数据,然后您可以在GetOverlappedResult()报告可用的情况下行走该缓冲区。

您的线程应该看起来更像这样:

void StartRead()
{
    DWORD dwBytes = 0;
    std::vector<BYTE> buffer(1024*64);
    OVERLAPPED o{0};
    bool bPending = false;
    //Be sure to set the hEvent member of the OVERLAPPED structure to a unique event.
    o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!o.hEvent) {
        CrAssert(0, CrWin32ErrorString());
    }
    while (m_Reading)
    {
        bPending = ReadDirectoryChangesW(m_hDIR,
            &buffer[0], buffer.size(),
            TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE,
            &dwBytes, &o, NULL);
        if (!bPending)
        {
            CrAssert(0, CrWin32ErrorString());
        }
        while (m_Reading)
        {
            if (GetOverlappedResult(m_hDIR, &o, &dwBytes, FALSE))
            {
                bPending = false;
                if (dwBytes != 0)
                {
                    FILE_NOTIFY_INFORMATION *fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(&buffer[0]);
                    do
                    {
                        if (fni->Action != 0)
                        {
                            std::wstring fileName(fni->FileName, fni->FileNameLength);
                            m_Callback(fileName);
                        }
                        if (fni->NextEntryOffset == 0)
                            break;
                        fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<BYTE*>(fni) + fni->NextEntryOffset);
                    }
                    while (true);
                }
                break;
            }
            if (GetLastError() != ERROR_IO_INCOMPLETE) {
                CrAssert(0, CrWin32ErrorString());
            }
            Sleep(10);
        }
        if (bPending)
        {
            CancelIo(m_hDIR);
            GetOverlappedResult(m_hDIR, &o, &dwBytes, TRUE);
        }
    }
    CloseHandle(o.hEvent);
}

在不定期轮询I/O状态的情况下实施此操作的另一种方法是摆脱m_Reading并使用可式的事件。当该线程应调用GetOverlappedResult()或终止时,让操作系统向线程发出信号,这样,它可以在其余时间睡觉的时间内不忙于做某事:

m_hDIR = CreateFileW(
            basePath,
            FILE_LIST_DIRECTORY | GENERIC_READ,
            FILE_SHARE_WRITE | FILE_SHARE_READ,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
            NULL);
if (m_hDIR == INVALID_HANDLE_VALUE)
    throw CrException(CrWin32ErrorString());
m_TermEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!m_TermEvent)
    throw CrException(CrWin32ErrorString());
//Start reading changes in background thread
m_Callback = std::move(a_Callback);
m_ReadThread = std::thread(&CrDirectoryWatcher::StartRead, this);
...
SetEvent(m_TermEvent);
m_ReadThread.join();

void StartRead()
{
    DWORD dwBytes = 0;
    std::vector<BYTE> buffer(1024*64);
    OVERLAPPED o{0};
    bool bPending = false, bKeepRunning = true;
    //Be sure to set the hEvent member of the OVERLAPPED structure to a unique event.
    o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!o.hEvent) {
        CrAssert(0, CrWin32ErrorString());
    }
    HANDLE h[2] = {o.hEvent, h_TermEvent};
    do
    {
        bPending = ReadDirectoryChangesW(m_hDIR,
            &buffer[0], buffer.size(),
            TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE,
            &dwBytes, &o, NULL);
        if (!bPending)
        {
            CrAssert(0, CrWin32ErrorString());
        }
        switch (WaitForMultipleObjects(2, h, FALSE, INFINITE))
        {
            case WAIT_OBJECT_0:
            {
                if (!GetOverlappedResult(m_hDIR, &o, &dwBytes, TRUE)) {
                    CrAssert(0, CrWin32ErrorString());
                }
                bPending = false;
                if (dwBytes == 0)
                    break;
                FILE_NOTIFY_INFORMATION *fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(&buffer[0]);
                do
                {
                    if (fni->Action != 0)
                    {
                        std::wstring fileName(fni->FileName, fni->FileNameLength);
                        m_Callback(fileName);
                    }
                    if (fni->NextEntryOffset == 0)
                         break;
                    fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<BYTE*>(fni) + fni->NextEntryOffset);
                }
                while (true);
                break;
            }
            case WAIT_OBJECT_0+1:
                bKeepRunning = false;
                break;
            case WAIT_FAILED:
                CrAssert(0, CrWin32ErrorString());
                break;
        }
    }
    while (bKeepRunning);
    if (bPending)
    {
        CancelIo(m_hDIR);
        GetOverlappedResult(m_hDIR, &o, &dwBytes, TRUE);
    }
    CloseHandle(o.hEvent);
}
相关文章:
  • 没有找到相关文章