【问题标题】:How can I safely terminate a worker thread in the middle of a WaitForMultipleObjects call from the main thread?如何在主线程的 WaitForMultipleObjects 调用中间安全地终止工作线程?
【发布时间】:2020-07-31 12:36:32
【问题描述】:

您好,我正在将 C++ 与 Qt 框架一起用于 Windows 程序。我使用 Qt 线程,但这种情况也可能与其他线程 API 有关。我正在使用一个工作线程来使用 Win API 中的 ReadDirectoryChangesWWaitForMultipleObjects 来监视目录更改。我希望能够从主线程优雅地取消工作线程。我读过关于 CancellIOEx 的内容,它带有一个句柄和 OVERLAPPED 参数,但这些数据类型都是指针。是否有一些安全的方法可以将这些指针从工作线程安全地传递到主线程?有没有更好的做事方式?

这是来自here 的一些代码,但使用WaitForSingleObject 而不是WaitForMultipleObjects,将从工作线程调用该函数。我可以从链接中发布此代码吗?另请参阅 here,了解有关 Windows 开发中心之外的 ReadDirectoryChangesW 的宝贵信息。

谢谢!

void WatchDirectory(LPCWSTR path)
{
   char buf[2048];
   DWORD nRet;
   BOOL result=TRUE;
   char filename[MAX_PATH];
   DirInfo[0].hDir = CreateFile (path, GENERIC_READ|FILE_LIST_DIRECTORY, 
                                 FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
                                 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED,
                                 NULL);

   if(DirInfo[0].hDir == INVALID_HANDLE_VALUE)
   {
       return; //cannot open folder
   }

   lstrcpy( DirInfo[0].lpszDirName, path);
   OVERLAPPED PollingOverlap;

   FILE_NOTIFY_INFORMATION* pNotify;
   int offset;
   PollingOverlap.OffsetHigh = 0;
   PollingOverlap.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
   while(result)
   {
       result = ReadDirectoryChangesW(
                  DirInfo[0].hDir,// handle to the directory to be watched
                  &buf,// pointer to the buffer to receive the read results
                  sizeof(buf),// length of lpBuffer
                  TRUE,// flag for monitoring directory or directory tree
                  FILE_NOTIFY_CHANGE_FILE_NAME |
                  FILE_NOTIFY_CHANGE_DIR_NAME |
                  FILE_NOTIFY_CHANGE_SIZE,
                //FILE_NOTIFY_CHANGE_LAST_WRITE |
                //FILE_NOTIFY_CHANGE_LAST_ACCESS |
                //FILE_NOTIFY_CHANGE_CREATION,
                &nRet,// number of bytes returned
                &PollingOverlap,// pointer to structure needed for overlapped I/O
                NULL);

       WaitForSingleObject(PollingOverlap.hEvent,INFINITE);
       offset = 0;
       int rename = 0;
       char oldName[260];
       char newName[260];
       do
       {
           pNotify = (FILE_NOTIFY_INFORMATION*)((char*)buf + offset);
           strcpy(filename, "");
           int filenamelen = WideCharToMultiByte(CP_ACP, 0, pNotify->FileName, pNotify->FileNameLength/2, filename, sizeof(filename), NULL, NULL);
           filename[pNotify->FileNameLength/2] = '';
           switch(pNotify->Action)
           {
               case FILE_ACTION_ADDED:
                   printf("\nThe file is added to the directory: [%s] \n", filename);
                   break;
               case FILE_ACTION_REMOVED:
                   printf("\nThe file is removed from the directory: [%s] \n", filename);
                   break;
               case FILE_ACTION_MODIFIED:
                   printf("\nThe file is modified. This can be a change in the time stamp or attributes: [%s]\n", filename);
                   break;
               case FILE_ACTION_RENAMED_OLD_NAME:
                   printf("\nThe file was renamed and this is the old name: [%s]\n", filename);
                   break;
               case FILE_ACTION_RENAMED_NEW_NAME:
                   printf("\nThe file was renamed and this is the new name: [%s]\n", filename);
                   break;
               default:
                   printf("\nDefault error.\n");
                   break;
            }

           offset += pNotify->NextEntryOffset;

        }while(pNotify->NextEntryOffset); //(offset != 0);
      }

    CloseHandle( DirInfo[0].hDir );

}

【问题讨论】:

  • event object 添加到您正在等待的句柄数组中,并在您希望工作线程终止时发出该事件的信号。
  • 谢谢,幸运的是,这是非常简单的东西:) 有没有关于这类事情的书或一些信息来源? Win API 有时可能有点难以捉摸......
  • 您只需要 2 本书即可快速掌握 Windows API:Charles Petzold 的 Programming Windows®, Fifth Edition 解释了应用程序开发的基本要素。它已经过时了,但几乎所有内容在今天仍然适用。对于系统编程,有 Jeffrey Richter 的Advanced Windows。这就是我所拥有的,但有一份修订后的出版物 (Windows via C/C++) 说明了操作系统的变化。你可能想要后者。
  • @IInspectable Event 对象有一个小问题。我从主线程创建事件,然后从工作线程打开事件。我得到 OpenEvent failed with error 5: Access is denied in the worker thread。默认安全描述符不应该足够吗?我做错了什么?
  • 想我明白了...关键部分是 EVENT_ALL_ACCESS OpenEvent(EVENT_ALL_ACCESS,FALSE,TEXT("MainEvent"));

标签: c++ multithreading qt winapi


【解决方案1】:

主线程可以创建OVERLAPPED 结构并将其提供给线程使用,而不是相反。但是,无论哪种方式,尝试从主线程取消 I/O 都将是一种竞争条件。由于您的工作线程必须在每个目录事件之后对ReadDirectoryChangesEx() 进行新调用,因此当主线程希望工作线程终止时,它可能在 调用ReadDirectoryChangesEx() 之间,从而调用@987654325 @ 当没有正在进行的 I/O 时将是空操作。

除了您为 I/O 创建的事件对象之外,还应创建另一个 event object 供主线程和工作线程共享。使用WaitForMultipleObjects() 让工作线程同时等待这两个事件,然后主线程可以在它希望工作线程终止时发出共享事件的信号。

WaitForMultipleObjects() 将告诉工作线程哪个事件已发出信号。如果共享事件发出信号,工作线程可以在退出前通过CancelIo/Ex() 取消其正在进行的 I/O。

// shared with both threads...
HANDLE hTermEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
// in main thread...
HANDLE hWorkerThread = CreateThread(...);
...
SetEvent(hTermEvent);
WaitForSingleObject(hWorkerThread, INFINITE);
// called by worker thread...
void WatchDirectory(LPCWSTR path)
{
   DWORD buf[512];
   DWORD nRet, dwRet;
   char filename[MAX_PATH];
   DirInfo[0].hDir = CreateFile(path, GENERIC_READ | FILE_LIST_DIRECTORY, 
                                 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                                 NULL);

   if (DirInfo[0].hDir == INVALID_HANDLE_VALUE)
   {
       return; //cannot open folder
   }

   lstrcpy(DirInfo[0].lpszDirName, path);

   OVERLAPPED PollingOverlap = {};
   PollingOverlap.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
   if (!PollingOverlap.hEvent)
   {
       return; //cannot create I/O event to wait on
   }

   FILE_NOTIFY_INFORMATION* pNotify;
   int offset;

   do
   {
       if (!ReadDirectoryChangesW(
              DirInfo[0].hDir,// handle to the directory to be watched
              &buf,// pointer to the buffer to receive the read results
              sizeof(buf),// length of lpBuffer
              TRUE,// flag for monitoring directory or directory tree
              FILE_NOTIFY_CHANGE_FILE_NAME |
              FILE_NOTIFY_CHANGE_DIR_NAME |
              FILE_NOTIFY_CHANGE_SIZE,
              //FILE_NOTIFY_CHANGE_LAST_WRITE |
              //FILE_NOTIFY_CHANGE_LAST_ACCESS |
              //FILE_NOTIFY_CHANGE_CREATION,
              &nRet,// number of bytes returned
              &PollingOverlap,// pointer to structure needed for overlapped I/O
              NULL))
       {
           break; // can't wait for an event
       }

       HANDLE events[] = {hTermEvent, PollingOverlap.hEvent};

       dwRet = WaitForMultipleObjects(2, events, FALSE, INFINITE);
       if (dwRet != (WAIT_OBJECT_0 + 1))
       {
           CancelIo(DirInfo[0].hDir);
           GetOverlappedResult(DirInfo[0].hDir, &PollingOverlap, &nRet, TRUE);
           break; // terminate requested, or wait failed
       }

       if (!GetOverlappedResult(DirInfo[0].hDir, &PollingOverlap, &nRet, TRUE))
       {
           break; // read failed
       }

       if (nRet == 0)
       {
           continue; // overflow, current event data discarded
       }

       offset = 0;
       int rename = 0;
       char oldName[MAX_PATH];
       char newName[MAX_PATH];
       do
       {
           pNotify = (FILE_NOTIFY_INFORMATION*) (buf + offset);
           int filenamelen = WideCharToMultiByte(CP_ACP, 0, pNotify->FileName, pNotify->FileNameLength/2, filename, sizeof(filename), NULL, NULL);
           switch (pNotify->Action)
           {
               case FILE_ACTION_ADDED:
                   printf("\nThe file is added to the directory: [%.*s] \n", filenamelen, filename);
                   break;
               case FILE_ACTION_REMOVED:
                   printf("\nThe file is removed from the directory: [%.*s] \n", filenamelen, filename);
                   break;
               case FILE_ACTION_MODIFIED:
                   printf("\nThe file is modified. This can be a change in the time stamp or attributes: [%.*s]\n", filenamelen, filename);
                   break;
               case FILE_ACTION_RENAMED_OLD_NAME:
                   printf("\nThe file was renamed and this is the old name: [%.*s]\n", filenamelen, filename);
                   break;
               case FILE_ACTION_RENAMED_NEW_NAME:
                   printf("\nThe file was renamed and this is the new name: [%.*s]\n", filenamelen, filename);
                   break;
               default:
                   printf("\nDefault error.\n");
                   break;
           }

           offset += pNotify->NextEntryOffset;
       }
       while (pNotify->NextEntryOffset);
   }
   while (true);

   CloseHandle(PollingOverlap.hEvent);
   CloseHandle(DirInfo[0].hDir);
}

【讨论】:

  • ReadDirectoryChangesW 等待指向双字对齐缓冲区的指针。 (更准确地对齐为FILE_NOTIFY_INFORMATION,它是 DWORD 对齐的)。 char buf[2048]; 在我的测试(使用 CL 编译器)中是否位于堆栈中,始终与 DWORD 对齐。但从形式上讲,这不是强制性的/保证的。如果buf 将成为结构的成员 - 它真的不能是 4 字节对齐(因为 char 数组不需要任何对齐)。因此,为了正式正确,例如需要将其声明为ULONG buf[xxx]。或者像 i 里面的 union { FILE_NOTIFY_INFORMATION fni; char buf[*];} 因为所有联合成员都有相同的对齐
  • 还有一个问题困扰着我……我可以重复使用相同的 CreateFile 句柄来分别调用 ReadDirectoryChangesW,一个用于文件修改,一个用于目录修改?我试过了,但程序开始出现不可预测的行为。我想将文件更改与目录更改分开,因为 ReadDirectoryChangesW 不会告诉您发生了什么。谢谢。
  • 我还在两个单独的调用之间重用了缓冲区和字节传输变量,这样可能会导致麻烦......
  • @riverofwind "我能否在两次单独调用 ReadDirectoryChangesW 时重复使用相同的 CreateFile 句柄" - 我不知道,我从未尝试过,但我的直觉是不,因为文档说它只为HANDLE 分配一个缓冲区来跟踪事件。但是,当不同的事件标志与不同的OVERLAPPED 结构相关联时,也许它能够区分。我只是不知道。 “我还在两个单独的调用之间重用了缓冲区和字节传输变量”——绝对不要那样做。两个单独的操作需要两组单独的变量来写入。
  • 经过一些试验,我发现如果我对 ReadDirectoryChangesW 的 dir 更改和文件更改调用重复使用相同的 CreateFile 句柄,则具有指定句柄的第一个调用将起作用,但第二个调用将永远不会在更改后触发要监视的目录的文件/目录结构。所以我尝试为每个 ReadDirectoryChangesW 调用使用唯一的 CreateFile 句柄,现在一切正常。
【解决方案2】:

终止线程永远不安全。这导致资源泄漏的程度如何。和 main - 您永远无法知道线程在何时终止。例如,它可以在堆分配/释放临界区内。并在此时终止它会导致下一次堆操作死锁(因为它的临界区永远不会释放)。

然而存在许多正确的解决方案,如何停止 I/O。当然可以使用 2 个在 cmets 中已经描述的特殊事件,但在我看来这不是最好的解决方案

1) 我们可以在文件句柄上使用CancelIoEx。当然仅仅调用CancelIoEx 是不够的——因为此时在专用线程中可能没有活动的I/O。还需要使用特殊标志(_bQuit)来取消任务,但这还不够。需要在临界区或使用ReadDirectoryChangesW/CancelIoEx 的破旧保护中检查/设置此标志

在专用线程中

AcquireSRWLockExclusive(this);

if (!_bQuit) // (1)
{
    ReadDirectoryChangesW(*); // (4)
}

ReleaseSRWLockExclusive(this);

为了停止

AcquireSRWLockExclusive(this);

_bQuit = true; // (2)
CancelIoEx(*); (3)

ReleaseSRWLockExclusive(this);

没有临界区或故障保护将可能在下一个顺序执行:

if (!_bQuit) // (1)
_bQuit = true; // (2)
CancelIoEx(*); (3)
ReadDirectoryChangesW(*); // (4)

可能是工作线程首先检查标志 _bQuit 并且它仍然为假的情况。然后主线程设置标志并调用CancelIoEx 这将不起作用,因为文件上没有 I/O。只有这样工作的线程调用ReadDirectoryChangesW 才会被取消。 但是通过使用临界区(广义上),我们使这成为不可能。所以可能只有2个订单: 或

if (!_bQuit) ReadDirectoryChangesW(*); // (1)
_bQuit = true; CancelIoEx(*); // (2)

在这种情况下ReadDirectoryChangesW 将被CancelIoEx 取消

_bQuit = true; CancelIoEx(*); // (1)
if (!_bQuit) ReadDirectoryChangesW(*); // (2)

在这种情况下,工作线程视图 _bQuit 标志设置,而不是调用 ReadDirectoryChangesW 更多。

完整的代码如下所示:

inline ULONG BOOL_TO_ERROR(BOOL f)
{
    return f ? NOERROR : GetLastError();
}

struct WatchFolder : SRWLOCK
{
    HANDLE _hThread, _hFile;
    BOOLEAN _bQuit;

    WatchFolder() : _hThread(0), _hFile(0), _bQuit(false)
    {
        InitializeSRWLock(this);
    }

    ~WatchFolder()
    {
        if (_hThread) {
            WaitForSingleObject(_hThread, INFINITE);
            CloseHandle(_hThread);
        }
        if (_hFile) CloseHandle(_hFile);
    }

    static ULONG CALLBACK _WatchDirectory(PVOID This)
    {
        reinterpret_cast<WatchFolder*>(This)->WatchDirectory();
        return 0;
    }

    void WatchDirectory()
    {
        OVERLAPPED ov {};

        if (ov.hEvent = CreateEvent(0, 0, 0, 0))
        {
            union {
                FILE_NOTIFY_INFORMATION fni;
                char buf[0x800];// must be aligned as FILE_NOTIFY_INFORMATION
            };

            for(;;) 
            {
                AcquireSRWLockExclusive(this);

                ULONG dwError = _bQuit ? ERROR_OPERATION_ABORTED : BOOL_TO_ERROR(
                    ReadDirectoryChangesW(_hFile, buf, sizeof(buf), TRUE, FILE_NOTIFY_VALID_MASK, 0, &ov, 0));

                ReleaseSRWLockExclusive(this);

                ULONG NumberOfBytesTransferred = 0;

                if (dwError == NOERROR)
                {
                    dwError = BOOL_TO_ERROR(GetOverlappedResult(_hFile, &ov, &NumberOfBytesTransferred, TRUE));
                }

                if (dwError || !NumberOfBytesTransferred)
                {
                    if (dwError != ERROR_OPERATION_ABORTED)
                    {
                        __nop();
                    }
                    break;
                }

                FILE_NOTIFY_INFORMATION* pNotify = &fni;

                ULONG NextEntryOffset = 0;
                do 
                {
                    (PBYTE&)pNotify += NextEntryOffset;

                    DbgPrint("%x %.*S\n", pNotify->Action, pNotify->FileNameLength / sizeof(WCHAR), pNotify->FileName);

                } while (NextEntryOffset = pNotify->NextEntryOffset);
            }

            CloseHandle(ov.hEvent);
        }
    }

    ULONG Start(PCWSTR szFile)
    {
        HANDLE hFile = CreateFileW(szFile, FILE_GENERIC_READ, FILE_SHARE_VALID_FLAGS,
            NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED, NULL);

        ULONG dwError;

        if (hFile != INVALID_HANDLE_VALUE)
        {
            if (_hThread = CreateThread(0, 0, _WatchDirectory, this, 0, 0))
            {
                _hFile = hFile;

                return NOERROR;
            }
            dwError = GetLastError();
            CloseHandle(hFile);
        }
        else
        {
            dwError = GetLastError();
        }

        return dwError;
    }

    void Stop()
    {
        AcquireSRWLockExclusive(this);

        _bQuit = true, CancelIoEx(_hFile, 0);

        ReleaseSRWLockExclusive(this);
    }
};

void test()
{
    WatchFolder wf;
    if (wf.Start(L"somepath") == NOERROR)
    {
        MessageBoxW(0,0,0,0);
        wf.Stop();
    }
}

2) 另一种方法是调用CloseHandle(_hFile) 而不是CancelIoEx(_hFile, 0);。当句柄(最后一个,但假设您只有一个句柄)关闭时 - 系统结束完成ReadDirectoryChangesW,状态为STATUS_NOTIFY_CLEANUP。代码将与案例CancelIoEx 非常相似,但现在终止时的错误将是ERROR_NOTIFY_CLEANUP 而不是ERROR_OPERATION_ABORTED。但如果使用GetOverlappedResult[Ex] 存在问题 - 此 api 在实施中有错误 - 它丢失了所有积极的状态值。它只是丢失 STATUS_NOTIFY_CLEANUP(但我们当然可以在OVERLAPPEDInternal 字段中查看它。代码可以在下一个:

            AcquireSRWLockExclusive(this);

            ULONG dwError = _bQuit ? ERROR_NOTIFY_CLEANUP : BOOL_TO_ERROR(
                ReadDirectoryChangesW(_hFile, buf, sizeof(buf), TRUE, FILE_NOTIFY_VALID_MASK, 0, &ov, 0));

            ReleaseSRWLockExclusive(this);

            ULONG NumberOfBytesTransferred = 0;

            if (dwError == NOERROR)
            {
                dwError = BOOL_TO_ERROR(GetOverlappedResult(_hFile, &ov, &NumberOfBytesTransferred, TRUE));
                // fix for error in GetOverlappedResult
                if (dwError == NOERROR && ov.Internal) dwError = RtlNtStatusToDosError((NTSTATUS)ov.Internal);
            }

            if (dwError || !NumberOfBytesTransferred)
            {
                if (dwError != ERROR_NOTIFY_CLEANUP)
                {
                    __nop();
                }
                break;
            }

为了停止

    AcquireSRWLockExclusive(this);

    _bQuit = true, CloseHandle(_hFile), _hFile = 0;

    ReleaseSRWLockExclusive(this);

3) 否则一个选项在GetOverlappedResultEx 内使用警报等待并插入 apc(或向工作线程发出警报)。在这种情况下,我们不需要使用临界区/或损坏保护 - 因为无论是在调用 ReadDirectoryChangesW 之前还是之后插入 apc(或警报) - 无论如何都会中断等待。

            ULONG dwError = _bQuit ? STATUS_USER_APC : BOOL_TO_ERROR(
                ReadDirectoryChangesW(_hFile, buf, sizeof(buf), TRUE, FILE_NOTIFY_VALID_MASK, 0, &ov, 0));

            ULONG NumberOfBytesTransferred = 0;

            if (dwError == NOERROR)
            {
                dwError = BOOL_TO_ERROR(GetOverlappedResultEx(_hFile, &ov, &NumberOfBytesTransferred, INFINITE, TRUE));
            }

            if (dwError || !NumberOfBytesTransferred)
            {
                if (dwError == STATUS_USER_APC)
                {
                    CancelIo(_hFile);
                    GetOverlappedResult(_hFile, &ov, &NumberOfBytesTransferred, TRUE);
                }
                break;
            }

我们需要停下来

static VOID NTAPI dummyAPC(_In_ ULONG_PTR )
{

}

_bQuit = true;
QueueUserAPC(dummyAPC, _hThread, 0);

当然改为调用dummyAPC(不需要)更好地使用警报,但GetOverlappedResultEx(更确切地说是WaitForSingleObjectEx)忽略STATUS_ALERT,并在它被STATUS_ALERT打断后再次开始等待。所以这里需要使用自定义代码

ULONG
WINAPI
GetOverlappedResult2( _In_ LPOVERLAPPED lpOverlapped,
                      _Out_ PULONG_PTR lpNumberOfBytesTransferred)
{
    while (lpOverlapped->Internal == STATUS_PENDING)
    {
        if (NTSTATUS status = ZwWaitForSingleObject(lpOverlapped->hEvent, TRUE, 0))
        {
            return RtlNtStatusToDosError(status);
        }
    }

    KeMemoryBarrier();

    *lpNumberOfBytesTransferred = lpOverlapped->InternalHigh;

    return RtlNtStatusToDosError((NTSTATUS)lpOverlapped->Internal);
}

并且可以使用下一个代码:

            ULONG dwError = _bQuit ? ERROR_ALERTED : BOOL_TO_ERROR(
                ReadDirectoryChangesW(_hFile, buf, sizeof(buf), TRUE, FILE_NOTIFY_VALID_MASK, 0, &ov, 0));

            ULONG_PTR NumberOfBytesTransferred = 0;

            if (dwError == NOERROR)
            {
                dwError = GetOverlappedResult2(&ov, &NumberOfBytesTransferred);
            }

            if (dwError || !NumberOfBytesTransferred)
            {
                if (dwError == ERROR_ALERTED)
                {
                    CancelIo(_hFile);
                    GetOverlappedResult(_hFile, &ov, (ULONG*)&NumberOfBytesTransferred, TRUE);
                }
                break;
            }

为了停止

_bQuit = true;
NtAlertThread(_hThread);

4) 然而,我的选择是最好的方法——不要全部使用专用线程,而是使用完整的异步 I/O。代码示例

struct WatchFolderCB : SRWLOCK, OVERLAPPED
{
    HANDLE _hFile;
    LONG _dwRefCount;
    union {
        FILE_NOTIFY_INFORMATION fni;
        char buf[0x800];// must be aligned as FILE_NOTIFY_INFORMATION
    };
    BOOLEAN _bQuit;

    void AddRef()
    {
        InterlockedIncrementNoFence(&_dwRefCount);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_dwRefCount))
        {
            delete this;
        }
    }

    WatchFolderCB() : _hFile(0), _bQuit(false), _dwRefCount(1)
    {
        InitializeSRWLock(this);
        RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
    }

    ~WatchFolderCB()
    {
        if (_hFile) CloseHandle(_hFile);
    }

    static VOID WINAPI _IoCompletionCallback(
        _In_    DWORD dwErrorCode,
        _In_    DWORD dwNumberOfBytesTransfered,
        _Inout_ LPOVERLAPPED lpOverlapped
        )
    {
        static_cast<WatchFolderCB*>(lpOverlapped)->IoCompletionCallback(
            RtlNtStatusToDosError(dwErrorCode), dwNumberOfBytesTransfered);
    }

    VOID IoCompletionCallback(DWORD dwErrorCode, DWORD NumberOfBytesTransferred)
    {
        if (dwErrorCode || !NumberOfBytesTransferred)
        {
            if (dwErrorCode != ERROR_NOTIFY_CLEANUP)
            {
                __nop();
            }
        }
        else
        {
            FILE_NOTIFY_INFORMATION* pNotify = &fni;

            ULONG NextEntryOffset = 0;
            do 
            {
                (PBYTE&)pNotify += NextEntryOffset;

                DbgPrint("%x %.*S\n", pNotify->Action, pNotify->FileNameLength / sizeof(WCHAR), pNotify->FileName);

            } while (NextEntryOffset = pNotify->NextEntryOffset);

            ReadChanges();
        }

        Release();
    }

    void ReadChanges()
    {
        AddRef();

        AcquireSRWLockExclusive(this);

        ULONG dwError = _bQuit ? ERROR_NOTIFY_CLEANUP : BOOL_TO_ERROR(
            ReadDirectoryChangesW(_hFile, buf, sizeof(buf), TRUE, FILE_NOTIFY_VALID_MASK, 0, this, 0));

        ReleaseSRWLockExclusive(this);

        if (dwError)
        {
            IoCompletionCallback(dwError, 0);
        }
    }

    ULONG Start(PCWSTR szFile)
    {
        HANDLE hFile = CreateFileW(szFile, FILE_GENERIC_READ, FILE_SHARE_VALID_FLAGS,
            NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED, NULL);

        ULONG dwError;

        if (hFile != INVALID_HANDLE_VALUE)
        {
            if (BindIoCompletionCallback(hFile, _IoCompletionCallback, 0))
            {
                _hFile = hFile;
                ReadChanges();
                return NOERROR;
            }
            dwError = GetLastError();
            CloseHandle(hFile);
        }
        else
        {
            dwError = GetLastError();
        }

        return dwError;
    }

    void Stop()
    {
        AcquireSRWLockExclusive(this);

        _bQuit = true, CloseHandle(_hFile), _hFile = 0;

        ReleaseSRWLockExclusive(this);
    }
};


void test1()
{
    if (WatchFolderCB* p = new WatchFolderCB)
    {
        if (p->Start(L"*") == NOERROR)
        {
            MessageBoxW(0,0,0,0);
            p->Stop();
        }
        p->Release();
    }
}

【讨论】:

  • @IInspectable 没有。我的具体代码正确。还有你在哪里查看访问非活动工会成员 - 非常有趣?
  • @IInspectable 好的。如果对你来说很难制定,我会解释你错误的地方。取对象的地址并不意味着访问对象。我采用 fniaddress 并正式记录所有工会成员都有 相同的 地址。所以&amp;fni == &amp;buf。我们也可以将&amp;fni 传递给ReadDirectoryChangesW 而不是&amp;buf - 没有什么不同 - api 采用PVOID (在这种情况下fni 将已经“活动”?为什么?我们既不能在代码中访问fni,也不能访问buf.真的buf在这里提供了缓冲区的大小(因为联合必须有足够的大小来容纳任何成员)和fni在这里提供align,因为它需要
  • @IInspectable 再次 - 我没有访问任何工会成员。我没有访问fni(我没有访问它的任何成员)并且我也没有访问buf(我没有使用buf[i])我只使用&amp;fni==&amp;buf地址,这是正式记录的一样。有趣 - 如果我写了 ReadDirectoryChangesW(_hFile, &amp;fni, sizeof(buf)(在 api 调用中将 buf 替换为 &amp;fni) - 现在 fni 将处于活动状态?!如果我这样做pNotify = (FILE_NOTIFY_INFORMATION*)buf; - 从你的角度来看这可以吗?并且是(FILE_NOTIFY_INFORMATION*)buf != &amp;fni ??
  • @IInspectable - 真的都是一样的,唯一的风格问题 - 使用类型转换 - (FILE_NOTIFY_INFORMATION*)buf 或使用联合获取 地址 并对齐。或者只是分配像(FILE_NOTIFY_INFORMATION*)new char[n] 这样的缓冲区。我们可以写ReadDirectoryChangesW(hfile, &amp;fni, sizeof(buf),ReadDirectoryChangesW(hfile, buf, sizeof(buf), - 两者相同。哪个更好看-另一个问题。我完全将ReadDirectoryChangesW 的第二个参数定义为PFILE_NOTIFY_INFORMATION 而不是PVOID - 这会更正确,但ms 定义为define
  • @IInspectable - &amp;fni - 这是fni不可访问fni.Action访问。喜欢和&amp;buf 不能访问。 buf[0] 何时访问。我还问了几个问题,但是好的,缩小到一个 - 如果我将代码更改为 ReadDirectoryChangesW(hfile, &amp;fni, sizeof(buf) - 从你的外观来看它已经是正确的了吗?绝对具体的问题。如果不是 - 这里还不行吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-13
  • 1970-01-01
  • 2019-05-03
  • 1970-01-01
  • 2011-11-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多