🌟

フォルダの監視

2023/06/11に公開

ReadDirectoryChangesW関数でフォルダ監視をする

他社システムとの連動にCSVファイルでの共有というのは現在でも意外と多い。
ファイル名が固定される場合はSetTimerで更新日付を監視する方が簡単だが、WindowsAPIにReadDirectoryChangesW関数という監視機能があったため、試しに利用してみることにした。

サンプル

メインスレッド側が終了フラグを立て、スレッド側がそれを取り込む箇所が(もっと簡単にできるだろうが)複雑。これはクラシックコンパイラで使用することを意識したため、排他処理で実装している。今考えればSync関数で取り込んでしまえばよかった。

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop
#include <System.Threading.hpp>

#include "Unit2.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm2 *Form2;
bool Cancel;  // スレッド終了フラグ
TMultiReadExclusiveWriteSynchronizer *ReadWriteLock;    // 排他制御オブジェクト
//---------------------------------------------------------------------------
__fastcall TForm2::TForm2(TComponent* Owner)
	: TForm(Owner)
{
    ReadWriteLock = new TMultiReadExclusiveWriteSynchronizer();
    ReadWriteLock->BeginWrite();
    Cancel = false;
    ReadWriteLock->EndWrite();
}
//---------------------------------------------------------------------------
void __fastcall TForm2::Button1Click(TObject *Sender)
{
_di_ITask task1 = TTask::Create([&](){
    // ディレクトリへのハンドルを作成
    HANDLE hDir = CreateFile(
        L"D:\\doc\\",
        FILE_LIST_DIRECTORY,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
        NULL);
    if ( hDir == INVALID_HANDLE_VALUE) {
        TThread::Synchronize(NULL,[=](){Memo1->Lines->Add("CreateFile failed.");});
        return;
    }

    // ディレクトリの変更検知を行うオブジェクトを作成
    //監視に使うフィルタ条件
    DWORD dwNotifyFilter =
      FILE_NOTIFY_CHANGE_FILE_NAME   |    //ファイル名変更
      FILE_NOTIFY_CHANGE_DIR_NAME    |    //ディレクトリ名変更
      FILE_NOTIFY_CHANGE_ATTRIBUTES  |    //属性変更
      FILE_NOTIFY_CHANGE_LAST_WRITE  |    //最終書き込み日時変更
      FILE_NOTIFY_CHANGE_LAST_ACCESS |    //最終アクセス日時変更
      FILE_NOTIFY_CHANGE_CREATION;        //作成日時変更

    wchar_t lpBuffer[1024];
    wchar_t lpAsync[1024];
    DWORD RetBytes;
    DWORD i = 0;
    HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    for(;;) {
        OVERLAPPED ovlp = {0};
        ResetEvent(hEvent);
        ovlp.hEvent = hEvent;

        bool bResult = ReadDirectoryChangesW(
            hDir,
            lpBuffer,
            1024,
            TRUE,
            FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
            NULL,
            &ovlp,
            NULL);
        if (!bResult) {
            TThread::Synchronize(NULL,[=](){Memo1->Lines->Add("ReadDirectoryChangesW failed.");});
            continue;
        }

        while ( [&](){ReadWriteLock->BeginRead(); bool ret = !Cancel; ReadWriteLock->EndRead(); return ret;}() ) {
            // 変更通知まち
            DWORD waitResult = WaitForSingleObject(hEvent, 500); // 0.5秒待ち
            if (waitResult != WAIT_TIMEOUT) {
                // 変更通知があった場合 (イベントがシグナル状態になった場合)
                break;
            }
        }
        ReadWriteLock->BeginRead();
        if ( Cancel == true ) {
            ReadWriteLock->EndRead();
            CloseHandle(hDir);
            delete ReadWriteLock;
            return;
        }
        ReadWriteLock->EndRead();
        DWORD retsize = 0;
        if (!GetOverlappedResult(hDir, &ovlp, &retsize, FALSE)) {
            DWORD Error = GetLastError();
            TThread::Synchronize(NULL,[=](){Memo1->Lines->Add("Error:");});
        }
        if (retsize == 0) {
            // 返却サイズ、0ならばバッファオーバーを示す
            TThread::Synchronize(NULL,[=](){Memo1->Lines->Add("overflow:");});
            continue;
        }
        FILE_NOTIFY_INFORMATION *pInfo = (FILE_NOTIFY_INFORMATION *)&lpBuffer[i];
        switch(pInfo->Action) {
            case FILE_ACTION_ADDED:
                TThread::Synchronize(NULL,[=](){Memo1->Lines->Add("file added:");});
                break;
            case FILE_ACTION_REMOVED:
                TThread::Synchronize(NULL,[=](){Memo1->Lines->Add("file deleted:");});
                break;
            case FILE_ACTION_MODIFIED:
                TThread::Synchronize(NULL,[=](){Memo1->Lines->Add("time stamp or attribute changed:");});
                break;
            case FILE_ACTION_RENAMED_OLD_NAME:
                TThread::Synchronize(NULL,[=](){Memo1->Lines->Add("file name changed - old name:");});
                break;
            case FILE_ACTION_RENAMED_NEW_NAME:
                TThread::Synchronize(NULL,[=](){Memo1->Lines->Add("file name changed - new name:");});
                break;
            default:
                TThread::Synchronize(NULL,[=](){Memo1->Lines->Add("unknown event:");});
                continue;
        }

        WCHAR FileName[256];
        lstrcpyn(FileName, pInfo->FileName, pInfo->FileNameLength / sizeof(WCHAR)+1);
        FileName[pInfo->FileNameLength / sizeof(WCHAR)] = '\0';
        TThread::Synchronize(NULL,[=](){Memo1->Lines->Add(FileName);});

        if(pInfo->NextEntryOffset == 0) continue;
        i += pInfo->NextEntryOffset;
    }
    CloseHandle(hEvent);
    CloseHandle(hDir);
});
task1->Start();
}
//---------------------------------------------------------------------------
void __fastcall TForm2::FormClose(TObject *Sender, TCloseAction &Action)
{

    ReadWriteLock->BeginWrite();
    Cancel = true;
    ReadWriteLock->EndWrite();
    // delete ReadWriteLock; // ここでdeleteしてはいけない!!スレッドが終了する前にReadWriteLockが解放され、スレッド側がハングアップする。
}
//---------------------------------------------------------------------------
void __fastcall TForm2::Button2Click(TObject *Sender)
{
    Close();
}
//---------------------------------------------------------------------------

Discussion