【小程序】文件夹监视器
acmilan2015/07/15软件综合 IP:四川
使用WinAPI监视文件夹变化:ReadDirectoryChangesW

实现界面(Visual C++ 2010):
捕获.png

用这个之前要先用CreateFile打开文件夹:
<code class="lang-cpp">m_dirhandle = ::CreateFile( // 打开所选目录!
            path, // 目录名
            FILE_LIST_DIRECTORY, // 以列出目录权限打开
            FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, // 允许其它程序读写删除
            NULL, // 没有安全选项
            OPEN_EXISTING, // 存在时打开
            FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED, // 备份存取异步存取
            NULL); // 没有文件模板</code>

然后新建线程进行监视:
<code class="lang-cpp">char notify[65536]; // 储存FILE_NOTIFY_INFORMATION链表的缓冲区
DWORD dwBytes; // 返回字节数
FILE_NOTIFY_INFORMATION *pnotify; // FILE_NOTIFY_INFORMATION指针
                 
DWORD monflag = FILE_NOTIFY_CHANGE_CREATION
            | FILE_NOTIFY_CHANGE_FILE_NAME
            | FILE_NOTIFY_CHANGE_LAST_WRITE; // 监控的内容
                 
while(true) // 监控循环
{
    memset(notify, 0, sizeof(notify)); // 清零缓冲区
    pnotify = (FILE_NOTIFY_INFORMATION*)notify; // 指向缓冲区开头的链表第一项
                 
    if (::ReadDirectoryChangesW( 
        pWnd->m_dirhandle, // 目录句柄
        notify, sizeof(notify), // 缓冲区及大小
        TRUE, // 是否监控子项目
        monflag, // 监控的内容
        &dwBytes, // 返回字节数
        NULL, NULL)) // 异步调用相关,这里是同步调用,因此全填NULL
    {
        if (dwBytes == 0) // 缓冲区溢出
        {
            // TODO: 缓冲区溢出,将无任何信息被返回
        }
        else
            while (true) // 链表迭代循环
            {
                // TODO: 处理返回的FILE_NOTIFY_INFORMATION结构
                 
                if (pnotify->NextEntryOffset) // 如果返回了另一个项目(通常是新文件名)
                {
                    pnotify = (FILE_NOTIFY_INFORMATION *)
                        ((char*)pnotify + pnotify->NextEntryOffset); // 迭代链表下一项
                    continue; // 继续
                }
                break; // 退出
            }
    }
}</code>

监视完成之后关闭文件夹:
<code class="lang-cpp">if (m_dirhandle) // 如果目录已被打开,则关闭目录
{
    CloseHandle(m_dirhandle);
    m_dirhandle = INVALID_HANDLE_VALUE;
}</code>

附件包含示例程序和源代码,基于VS2010和MFC
attachment icon FolderMonitor.rar 826.44KB RAR 19次下载
来自:计算机科学 / 软件综合
3
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
acmilan 作者
9年5个月前 IP:四川
779026
向导生成的所有控件变量:
<code class="lang-cpp">void CFolderMonitorDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_BTN_START, m_startbutton);
    DDX_Control(pDX, IDC_EDIT1, m_folderedit);
    DDX_Control(pDX, IDC_MFCSHELLTREE1, m_foldertree);
    DDX_Control(pDX, IDC_LIST1, m_resultlist);
    DDX_Control(pDX, IDC_CHECK1, m_flag_filename);
    DDX_Control(pDX, IDC_CHECK2, m_flag_dirname);
    DDX_Control(pDX, IDC_CHECK3, m_flag_attr);
    DDX_Control(pDX, IDC_CHECK4, m_flag_size);
    DDX_Control(pDX, IDC_CHECK5, m_flag_lastwrite);
    DDX_Control(pDX, IDC_CHECK6, m_flag_lastaccess);
    DDX_Control(pDX, IDC_CHECK7, m_flag_creation);
    DDX_Control(pDX, IDC_CHECK8, m_flag_security);
}</code>

其它变量:
<code class="lang-cpp">CString m_getpath;
CString m_getopr;
CString m_gettime;
CWinThread *m_monthread;
HANDLE m_dirhandle;
CMutex m_mutex;</code>

消息映射:
<code class="lang-cpp">#define WM_TRHEADMSG (WM_USER + 8)
BEGIN_MESSAGE_MAP(CFolderMonitorDlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_NOTIFY(TVN_SELCHANGED, IDC_MFCSHELLTREE1, &CFolderMonitorDlg::OnTvnSelchangedMfcshelltree1)
    ON_BN_CLICKED(IDC_BTN_START, &CFolderMonitorDlg::OnBnClickedBtnStart)
    ON_MESSAGE(WM_TRHEADMSG, &CFolderMonitorDlg::ThreadMsg)
    ON_WM_DESTROY()
    ON_COMMAND_RANGE(IDC_CHECK1, IDC_CHECK8, &CFolderMonitorDlg::OnCheckBox)
END_MESSAGE_MAP()</code>

核心代码(FolderMonitorDlg.h):
<code class="lang-cpp">//////////////////////////////////////////////////////////////////////////////////////
 
//BOOL CFolderMonitorDlg::OnInitDialog()
//{
//  ResetComponents();
//}
void CFolderMonitorDlg::ResetComponents(void)
{
    while (m_resultlist.DeleteColumn(0)); // 删除原有表列
    m_resultlist.InsertColumn(0, _T("路径"), 0, 200); // 添加表列
    m_resultlist.InsertColumn(1, _T("操作"), 0, 80);
    m_resultlist.InsertColumn(2, _T("时间"), 0, 80);
 
    m_resultlist.ModifyStyle(LVS_TYPEMASK, LVS_REPORT); // 设置为列表式
    DWORD exstyle = m_resultlist.GetExtendedStyle(); // 设置为全行选择、有格线
    m_resultlist.SetExtendedStyle(exstyle|LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);
 
    m_flag_filename.SetCheck(true); // 设置默认监控项目
    m_flag_creation.SetCheck(true);
    m_flag_lastwrite.SetCheck(true);
    OnCheckBox(IDC_CHECK1); // 更新m_monflag
}
 
void CFolderMonitorDlg::OnTvnSelchangedMfcshelltree1(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMTREEVIEW pNMTreeView = reinterpret_cast<lpnmtreeview>(pNMHDR);
    // TODO: 在此添加控件通知处理程序代码
    *pResult = 0;
    CString path;
    m_foldertree.GetItemPath(path);
    m_folderedit.SetWindowText(path); // 根据文件夹树所选项目,更新文本框
}
 
UINT MonitorThread(LPVOID pvoid);
 
void CFolderMonitorDlg::OnBnClickedBtnStart()
{
    // TODO: 在此添加控件通知处理程序代码
    CString btntext;
    m_startbutton.GetWindowText(btntext);
    if (btntext == _T("开始"))
    {
        CString path;
        m_folderedit.GetWindowText(path); // 获取文本框中的字符串
 
        m_dirhandle = ::CreateFile( // 打开所选目录!
            path, // 目录名
            FILE_LIST_DIRECTORY, // 以列出目录权限打开
            FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, // 允许其它程序读写删除
            NULL, // 没有安全选项
            OPEN_EXISTING, // 存在时打开
            FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED, // 备份存取异步存取
            NULL); // 没有文件模板
        if (m_dirhandle == INVALID_HANDLE_VALUE) // 如果打开失败则退出
            return;
 
        m_monthread = new CWinThread(MonitorThread, this); // 新建并启动工作线程
        m_monthread->CreateThread();
 
        m_startbutton.SetWindowText(_T("停止"));
    }
    else if (btntext == _T("停止"))
    {
        OnDestroy(); // 停止工作线程并清理资源
        m_startbutton.SetWindowText(_T("清除"));
    }
    else if (btntext == _T("清除"))
    {
        m_resultlist.DeleteAllItems(); // 清空列表框
        m_startbutton.SetWindowText(_T("开始"));
    }
    else
    {
        AfxMessageBox(_T("出现了不该出现的情况"));
        m_startbutton.SetWindowText(_T("停止"));
    }
}
 
void CFolderMonitorDlg::OnDestroy()
{
    if (m_monthread != NULL) // 如果线程存在,则终止线程
    {
        if (m_monthread->m_hThread != INVALID_HANDLE_VALUE)
            m_monthread->SuspendThread();
        delete m_monthread;
        m_monthread = NULL;
    }
    if (m_dirhandle != INVALID_HANDLE_VALUE) // 如果目录已打开,则关闭目录
    {
        CloseHandle(m_dirhandle);
        m_dirhandle = INVALID_HANDLE_VALUE;
    }
}
 
//BEGIN_MESSAGE_MAP(CFolderMonitorDlg, CDialogEx)
//  ON_COMMAND_RANGE(IDC_CHECK1, IDC_CHECK8, &CFolderMonitorDlg::OnCheckBox)
//END_MESSAGE_MAP()
void CFolderMonitorDlg::OnCheckBox(UINT nID)
{
    m_mutex.Lock(); // 赋值时可能会被工作线程异步读取,所以要上锁
    m_monflag = 0;
    if (m_flag_attr.GetCheck()) m_monflag |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
    if (m_flag_creation.GetCheck()) m_monflag |= FILE_NOTIFY_CHANGE_CREATION;
    if (m_flag_dirname.GetCheck()) m_monflag |= FILE_NOTIFY_CHANGE_DIR_NAME;
    if (m_flag_filename.GetCheck()) m_monflag |= FILE_NOTIFY_CHANGE_FILE_NAME;
    if (m_flag_lastaccess.GetCheck()) m_monflag |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
    if (m_flag_lastwrite.GetCheck()) m_monflag |= FILE_NOTIFY_CHANGE_LAST_WRITE;
    if (m_flag_security.GetCheck()) m_monflag |= FILE_NOTIFY_CHANGE_SECURITY;
    if (m_flag_size.GetCheck()) m_monflag |= FILE_NOTIFY_CHANGE_SIZE;
    m_mutex.Unlock();
}
 
UINT MonitorThread(LPVOID pvoid)
{
    CFolderMonitorDlg *pWnd = (CFolderMonitorDlg*)pvoid;
 
    char notify[65536]; // 储存FILE_NOTIFY_INFORMATION链表的缓冲区
    DWORD dwBytes; // 返回字节数
    FILE_NOTIFY_INFORMATION *pnotify; // FILE_NOTIFY_INFORMATION指针
 
    if (pWnd->m_dirhandle == INVALID_HANDLE_VALUE) // 如果文件夹没打开则直接结束
        return 1;
 
    while(true) // 监控循环
    {
        memset(notify, 0, sizeof(notify)); // 清零缓冲区
        pnotify = (FILE_NOTIFY_INFORMATION*)notify; // 指向缓冲区开头的链表第一项
 
        pWnd->m_mutex.Lock(); // 因为可能会被主线程异步赋值,所以要进行锁定
        DWORD monflag = pWnd->m_monflag; // 获取flag(见OnCheckBox)
        pWnd->m_mutex.Unlock();
 
        if (::ReadDirectoryChangesW( 
            pWnd->m_dirhandle, // 目录句柄(见OnBnClickedBtnStart)
            ¬ify, sizeof(notify), // 缓冲区及大小
            TRUE, // 是否监控子项目
            monflag, // 监控的内容(见OnCheckBox)
            &dwBytes, // 返回字节数
            NULL, NULL)) // 异步调用相关,这里是同步调用,因此全填NULL
        {
            if (!dwBytes) // 如果缓冲区溢出
            {
                pWnd->m_getpath = _T("[缓冲区溢出]");
                pWnd->m_getopr = _T("未知");
 
                SYSTEMTIME st;
                GetLocalTime(&st);
                pWnd->m_gettime = CTime(st).Format("%H:%M:%S"); // 获取系统时间
 
                pWnd->SendMessage(WM_TRHEADMSG, 0, 0); // 通知主线程添加列表项
            }
            else
                while(true) // 链表迭代循环
                {
                    LPWSTR wbuf = pWnd->m_getpath.GetBufferSetLength(pnotify->FileNameLength / 2);
                    memcpy(wbuf, pnotify->FileName, pnotify->FileNameLength);
                    pWnd->m_getpath.ReleaseBuffer(pnotify->FileNameLength / 2); // 获取文件名
 
                    pWnd->m_getopr = // 获取操作类型
                        pnotify->Action == FILE_ACTION_ADDED ? _T("新建") :
                        pnotify->Action == FILE_ACTION_MODIFIED ? _T("更改") :
                        pnotify->Action == FILE_ACTION_REMOVED ? _T("删除") :
                        pnotify->Action == FILE_ACTION_RENAMED_OLD_NAME ? _T("重命名") :
                        pnotify->Action == FILE_ACTION_RENAMED_NEW_NAME ? _T("新名称") :
                        _T("未知");
 
                    SYSTEMTIME st;
                    GetLocalTime(&st);
                    pWnd->m_gettime = CTime(st).Format("%H:%M:%S"); // 获取系统时间
 
                    pWnd->SendMessage(WM_TRHEADMSG, 0, 0); // 通知主线程添加列表项
 
                    if (pnotify->NextEntryOffset) // 如果返回了另一个项目(通常是新文件名)
                    {
                        pnotify = (FILE_NOTIFY_INFORMATION *)
                            ((char*)pnotify + pnotify->NextEntryOffset); // 迭代链表下一项
                        continue; // 继续
                    }
                    break; // 退出
                }
        }
    }
}
 
//#define WM_THREADMSG (WM_USER + 8)
//BEGIN_MESSAGE_MAP(CFolderMonitorDlg, CDialogEx)
//  ON_MESSAGE(WM_TRHEADMSG, &CFolderMonitorDlg::ThreadMsg)
//END_MESSAGE_MAP()
LRESULT CFolderMonitorDlg::ThreadMsg(WPARAM wParam, LPARAM lParam)
{
    // 工作线程调用此消息以插入列表项
    m_resultlist.InsertItem(0, m_getpath); // 根据返回的字符串插入表项,设置其它列的值
    m_resultlist.SetItemText(0, 1, m_getopr);
    m_resultlist.SetItemText(0, 2, m_gettime);
    return TRUE;
}</lpnmtreeview></code>

(完)
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
acmilan作者
9年5个月前 IP:四川
779030
这个ReadDirectoryChangesW的蛋疼之处在于:
1.返回一个链表缓冲区,而不是直接返回一个结构
2.如果缓冲区不够大,那么lpReturnedBytes为0,并且缓冲区里什么也没有
XXXXotify->FileName字符串只能是Unicode字符串wchar_t,并且不以L'\0'结尾
XXXXotify->FileNameLength返回字节数而不是字符数
5.文件被更改时会收到重复的通知,在程序中应用时需要做延时处理
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
上级专业
同级专业
acmilan
进士 学者 笔友
文章
461
回复
2934
学术分
4
2009/05/30注册,5年10个月前活动
暂无简介
主体类型:个人
所属领域:无
认证方式:邮箱
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}