潇潇2003http://blog.yesky.com/Blog/tiger12/复制地址
控制面板
日历
<2008年9月>
SuMoTuWeThFrSa
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011
留言簿(1)
文章档案
我的其他blog

Win32中的目录监控

Windows里面对目录、文件监控提供了两个API,它们分别是:FindFirstChangeNotificationReadDirectoryChangesW

1FindFirstChangeNotification
HANDLE FindFirstChangeNotification(
  LPCTSTR lpPathName,
  BOOL bWatchSubtree,
  DWORD dwNotifyFilter
);

API能够监控文件名、目录名、文件属性、子文件夹、文件大小、文件的最后写时间、安全属性的改变。该函数返回的是一个监控句柄(Notification Handle),该句柄能够被 WaitForMultipleObjects 其上进行等待,当该句柄所监控的条件满足时,该句柄就会处于激发状态,这时用户程序就能够知道该目录发生了变化。在进行了用户自己的处理之后,一定要调用BOOL FindNextChangeNotification(HANDLE hChangeHandle)来将该监控句柄置为去激活状态,并继续等待下一次被激活。如果你不再想监控了,就要调用FindCloseChangeNotification关闭监控句柄。

下面是MSDN上的一个例子:

**********************FindFirstChangeNotification********************

DWORD dwWaitStatus;

HANDLE dwChangeHandles[2];

 

// Watch the C:\WINDOWS directory for file creation and

// deletion.

 

dwChangeHandles[0] = FindFirstChangeNotification(

    "C:\\WINDOWS",                 // directory to watch

    FALSE,                         // do not watch the subtree

    FILE_NOTIFY_CHANGE_FILE_NAME); // watch file name changes

 

if (dwChangeHandles[0] == INVALID_HANDLE_VALUE)

    ExitProcess(GetLastError());

 

// Watch the C:\ subtree for directory creation and

// deletion.

 

dwChangeHandles[1] = FindFirstChangeNotification(

    "C:\\",                        // directory to watch

    TRUE,                          // watch the subtree

    FILE_NOTIFY_CHANGE_DIR_NAME);  // watch dir. name changes

 

if (dwChangeHandles[1] == INVALID_HANDLE_VALUE)

    ExitProcess(GetLastError());

 

// Change notification is set. Now wait on both notification

// handles and refresh accordingly.

 

while (TRUE)

{

 

    // Wait for notification.

 

    dwWaitStatus = WaitForMultipleObjects(2, dwChangeHandles,

        FALSE, INFINITE);

 

    switch (dwWaitStatus)

    {

        case WAIT_OBJECT_0:

 

        // A file was created or deleted in C:\WINDOWS.

        // Refresh this directory and restart the

        // change notification. RefreshDirectory is an

        // application-defined function.

 

            RefreshDirectory("C:\\WINDOWS")

            if ( FindNextChangeNotification(

                    dwChangeHandles[0]) == FALSE )

                ExitProcess(GetLastError());

            break;

 

        case WAIT_OBJECT_0 + 1:

 

        // A directory was created or deleted in C:\.

        // Refresh the directory tree and restart the

        // change notification. RefreshTree is an

        // application-defined function.

 

            RefreshTree("C:\\");

            if (FindNextChangeNotification(

                    dwChangeHandles[1]) == FALSE)

                ExitProcess(GetLastError());

            break;

 

        default:

            ExitProcess(GetLastError());

    }

}

*********************************************************************


2
ReadDirectoryChangesW

BOOL ReadDirectoryChangesW(
  HANDLE hDirectory,
  LPVOID lpBuffer,
  DWORD nBufferLength,
  BOOL bWatchSubtree,
  DWORD dwNotifyFilter,
  LPDWORD lpBytesReturned,
  LPOVERLAPPED lpOverlapped,
  LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

这个函数的参数很多,但是在某种特定的使用场合下,通常不会用到全部的参数,也就前面的5个。

该函数提供了同步和异步的两大类方法,异步方法里面又有三种不同异步处理机制(注意:这里所说的同步或异步指的是操作系统提供的同步或异步机制,在用户程序看来使用了操作系统同步机制的代码仍然可以是异步的。)

先来看看第一个参数hDirectory,从字面上看来是一个目录句柄,其实是一个文件句柄,在操作系统的眼里目录也只是一种特殊的文件,因此该句柄的获得需要调用CreateFile函数:

 
hDir = CreateFile(
  DirName,                            // pointer to the file name
  FILE_LIST_DIRECTORY,                // access (read/write) mode 打开目录
  FILE_SHARE_READ|FILE_SHARE_DELETE,  // share mode
  NULL,                               // security descriptor
  OPEN_EXISTING,                      // how to create
  FILE_FLAG_BACKUP_SEMANTICS,         // file attributes 打开目录必须的选项
  NULL                                // file with attributes to copy
);
 

知道了目录句柄如何获得的,就可以看看同步方式如何监控目录了。

首先我们浏览一下其他的参数,从最简单的布尔变量看起,bWatchSubtree一眼就能够看出是:是否监控子目录。OK,再看DWORD dwNotifyFilter这个参数肯定是目录变化通知过滤器了,查看MSDN文档知道修改文件名的过滤器选项就是FILE_NOTIFY_CHANGE_FILE_NAME,修改文件就是FILE_NOTIFY_CHANGE_LAST_WRITE,等等。

然后要了解其他的参数有何意义就要先了解一下操作系统把目录变化通知是怎样交给用户代码的。通知是放在哪里?有些什么内容?它的内存是由谁分配的?如果是由用户分配的(这是最自然的一种想法了),又是怎样告知操作系统的?好了,想到这里,我想第二、第三个参数的意义已经很明确了,用户代码就是通过这两个参数来告知操作系统该把目录变化通知放在首地址为lpBuffer, 长度为nBufferLength的一块内存当中的。但是该内存又是怎样组织的呢?操作系统是把他们放在FILE_NOTIFY_INFORMATION这个结构里面的:

 
typedef struct _FILE_NOTIFY_INFORMATION {

          DWORD NextEntryOffset;

          DWORD Action;

          DWORD FileNameLength;

          WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;
 

有经验的人可以很快看出这其实是一个链表结构,第一个字段存储了要获得下一个记录需要跳过多少字节数,如果它的值为0,就表示本记录已经是链表中的最后一条记录了。该字段其实也可以看做是一个指向下一条记录的指针。第二个字段的含义是:本次通知了哪种类型的目录变化。第三个字段表示的是变化的文件或者目录的名称的长度。第四个字段是一个Unicode字符数组的首地址。

哦,知道了目录变化通知是放在一个链表里面的,而且知道这个链表的首地址是lpBuffer,那么这个链表总共有多大呢?这个数值是放在LPDWORD lpBytesReturned参数里面的。

好了,同步监控所涉及到的参数就这么多了,那同步监控的具体步骤呢?很简单:

a)     使用CreateFile获取目录句柄

b)      在一个While循环里面调用ReadDirectoryChangesW并且把自己分配的内存首地址、内存长度、目录句柄传给该函数。用户代码在该函数的调用中进行同步等待。

这样一旦有了目录变化,操作系统便会从对ReadDirectoryChangesW的调用中返回,并把目录变化通知放在了用户指定的位置。

 

******************************DirMonitor.h***************************

#pragma once

 

#ifndef _DIR_MONITOR_H_

#define _DIR_MONITOR_H_

 

#include <ace/os.h>

#include <ace/Task_T.h>

 

class DirMonitor : public ACE_Task<ACE_MT_SYNCH>

{

private:

        HANDLE m_hCompletionPort;

        OVERLAPPED m_Overlapped;

        HANDLE m_hDir;

        ULONG m_ulCompletionKey;

        std::string m_strMoniDir;

        std::string m_strCacheDir;

        char m_pBuf[1024];

        DWORD m_dwBytesReturned;

public:

        DirMonitor(ACE_Thread_Manager *thr_mgr, std::string strDirName)

               : ACE_Task<ACE_MT_SYNCH> (thr_mgr)

        {

               m_strMoniDir = strDirName;

 

               m_hDir = CreateFile(

                       m_strMoniDir.c_str()                // pointer to the file name

                       FILE_LIST_DIRECTORY,                // access (read/write) mode

                       FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,  // share mode

                       NULL,                               // security descriptor

                       OPEN_EXISTING,                      // how to create

                       FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED,   // file attributes

                       NULL                                // file with attributes to copy

               );

 

               if ( INVALID_HANDLE_VALUE == m_hDir )

               {

                       ExitProcess(GetLastError());

               }

 

               ACE_OS::memset( &m_Overlapped, 0, sizeof(m_Overlapped) );

              

        }

 

        ~DirMonitor(void)

        {

               CloseHandle(m_hDir);

#ifdef ASYN_CALL

               //kill off the thread

               PostQueuedCompletionStatus(m_hCompletionPort, 0, 0, NULL);//The thread will catch this and exit the thread

               //wait for it to exit

               WaitForSingleObject(this->get_handle(), INFINITE);

//             delete m_lpOverlapped;

 

#endif // ASYN_CALL

        }

 

        int open (void *args);

        int svc(void);

};

 

#endif //_DIR_MONITOR_H_

 

*********************************************************************

****************************DirMonitor.cpp***************************

#include "stdafx.h"

#include <sstream>

#include <iostream>

#include "DirMonitor.h"

 

int DirMonitor::open (void *args)

{

#ifdef ASYN_CALL

        m_hCompletionPort = CreateIoCompletionPort(

               m_hDir,                        // Handle to a file opened for overlapped I/O completion.

               NULL,                          // existed Handle to the I/O completion port.

               (ULONG_PTR)&m_ulCompletionKey, // CompletionKey

               1                                     // NumberOfConcurrentThreads

               );

 

        ACE_OS::memset( m_pBuf, 0, sizeof(char) * 1024 );

        BOOL bSucceed = ReadDirectoryChangesW(

               m_hDir,                        // hDirectory. Handle to the directory to be monitored

               m_pBuf,                        // [in, out] Pointer to the formatted buffer in which the read results are to be returned.

               1024,                          // [in] Length of the buffer pointed to by the lpBuffer parameter, in bytes.

               FALSE,                                               // bWatchSubtree

               FILE_NOTIFY_CHANGE_LAST_WRITE, // [in] Filter criteria the function checks to determine if the wait operation has completed.

               NULL,                                                // lpBytesReturned. For asynchronous calls, this parameter is undefined.

               &m_Overlapped,                                // [in] Pointer to an OVERLAPPED structure that supplies data to be used during asynchronous operation.

               NULL                                                 // lpCompletionRoutine

               );

#endif // ASYN_CALL

 

        if (this->activate (THR_NEW_LWP,

                    1,

                    0,

                    -1,

                    -1,

                    this) == -1)

        ACE_ERROR ((LM_ERROR,

                               "%p\n",

                               "activate failed"));