3

はじめから出るのスレッド:

私が新たに追加されたファイルの特定のディレクトリを監視する小さなアプリケーションを書いていますが。非同期ReadDirectoryChangesW通話ブロック

監視コードを別のスレッドに入れたいので、メインスレッドを他のもののために空けておき、必要なときに監視スレッドをキャンセルすることができます。

関連情報:

  • 私は以降のWindows XPをサポートしようとしているスレッドの作成/同期
  • のための生のWin32 APIを使用しています監視
  • を行うにはReadDirectoryChangesWを使用しています。

問題:

は、私は一つのことを除いて、適切にすべてのものをコーディングすることができました:

私は、したがって、適切にこのポストをスレッドの監視を終了することはできません。

私はメインスレッドでイベントオブジェクトを通知し、スレッドが終了してからクリーンアップを行うのを待ちます。

ReadDirectoryChangesWの問題は、そのコードをコメントアウトした後に問題なく動作するためです。

イベントハンドルが通知されると、ReadDirectoryChangesWはスレッドをブロックし、イベントを「キャッチ」して終了します。 ReadDirectoryChangesWというディレクトリに新しいファイルを追加すると、スレッドはそのイベントを「キャッチ」して終了します。

私はこれまでに述べたことを具体的に示すために、さらに小さな数字をMVCEとしました。

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完了ポートを使用することができましたが、私は1つのディレクトリだけを監視するので、これはちょっとした過ちです。私が間違っている場合は、私のケースにI/O完了ポートを使用する方法を教えてください、私はそれらを試してみたいと思います。

QUESTION:

あなたはそれが( ReadDirectoryChangesWブロックせずに)適切に終了しますので、糸の手順でコードを変更する方法の私を指示することができ、MVCE上記の

私は完了ルーチンを使用しなければならないと感じています。その場合、擬似コードや書面による指示を謙虚に尋ねるのは、これを初めて使用するためです。

私が進歩を遂げたと感じる度に、それに応じてこの投稿を更新します。

さらなる情報や説明が必要な場合は、コメントを残して返信します。

ありがとう、

よろしく。 - あなたは最悪のバリアントを選択

最悪

  • 使用ApcRoutine
  • 使用IoCompletionPort
  • 使用イベント:

+0

奇妙なエンドレスループイベントは何ですか?貨物カルトプログラミングのような匂い。 –

+0

@ JonathanPotter:これらのエラーを処理するプログラムフローを正しく構造化する方法がわかりません。質問に記載されている実際の問題を手伝ってもらえますか? – AlwaysLearningNewStuff

+0

@ JonathanPotter:私はあなたの提案に従ってコードを書き直しました... – AlwaysLearningNewStuff

答えて

1

は、ファイルに非同期操作を行うための3つの方法が存在します。私はあなたのところでIoCompletionPortを使用しています。このケースでは、イベント、スレッドを作成する必要はありません。を呼び出すと、ループは必要ありません。

すべてのファイルにはBindIoCompletionCallback(またはRtlSetIoCompletionCallback)が必要です。キャンセルについて

- この場合にはIOがSTATUS_NOTIFY_CLEANUPでキャンセルされます - CancelIoExは、( 「私はWindows XPをサポートしようとしています」) しかし、あなたは、単に近いディレクトリハンドルをすることができますXPには存在しません。すべてのIO iが​​を使用し終えたときに、待機用

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(); 
    } 
} 

:そうコードは次のように見ることができます。残念ながら、これはユーザモードでは実装されていませんが、この非常に便利な機能をハードに実装するのではありません。私の実装:

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(); 
    } 
}; 
+0

まず、お返事ありがとうございます。 *あなたは最悪の変種を選択します。私はあなたの場所にIoCompletionPortを使用しています*追加された新しいファイルだけを監視するために、1つのフォルダに対してのみIOCPを作成するという利点はありませんでした。しかし、私は現在、 'BindIoCompletionCallback'のドキュメントを読んでいます(これまで聞いたことはありません)。あなたのアプローチがよりよく見えると感じます。あなたが提供したリンクを読み、コードを研究する時間を与えてください。私は実際に "IO完了ポート"ルートに行きたいと思っています。 – AlwaysLearningNewStuff

+0

@ AlwaysLearningNewStuffどのような問題がIOCPを作成しますか(現在のケースでは間接的です)?どのような異なる1つのフォルダまたは多く。イベントをより良く作成する?ケースASIOの場合にも、追加の文脈はありません。 APCまたはIOCPの場合このイベントと麦汁のためです。イベントの場合は待つ必要があります。それ以外の場合、私たちは待つことはありません - 単純に呼び出されるコールバックです。もちろん、IOCP(BindIoCompletionCallback)を使用する前に絶対に私のコードを理解することは難しくなります。 – RbMm

+0

*使用する前に決して使用しない場合は、IOCP(BindIoCompletionCallback)は自分のコードを理解しにくいでしょう。私は私の会社の上級開発者から(私のOPからの)仕事を得たので、私は自分ですべてをやっていました。私が投稿した解決策を思いついたが、それが最良のものではないことは分かっていた。それでも、私はあなたのコードを理解するための努力をします。なぜなら、IOCPが最良のルートであることに同意するからです。私は、それらを適切に実装する方法を知りません。再度、感謝します。 – AlwaysLearningNewStuff

関連する問題