编写简易目录监控的心得
来源:互联网 发布:bp神经网络算法 公式 编辑:程序博客网 时间:2024/05/21 21:46
目录监控主要用WinAPI函数ReadDirectoryChangeW实现对目录中的文件进行删除,修改,重命名功能监控,并将监控信息显示在界面上.用户可以选择多个目录进行监控.主界面如图1所示:
图1
功能实现:
- 目录选择(如图2所示)
实现函数:
PIDLIST_ABSOLUTE SHBrowseForFolder( LPBROWSEINFO lpbi)
参数:结构体指针.
返回值:PIDL..PIDL表示所选目录相对于跟的位置.(需要用SHGetPathFromIDList函数将PIDL转换为具体路径.如果选择取消,返回NULL
char szPath[MAX_PATH];CString str;ZeroMemory(szPath,sizeof(szPath));BROWSEINFO bi;bi.hwndOwner=m_hWnd;bi.pidlRoot=NULL;bi.pszDisplayName=szPath;bi.lpszTitle="请选择监控的目录!";bi.ulFlags=0;bi.lpfn=NULL;bi.lParam=0;bi.iImage=0;LPITEMIDLIST lp=SHBrowseForFolder(&bi);if(lp){if(SHGetPathFromIDList(lp,szPath)){str.Format("你选择的目录是:\n%s\n是否开始监听?",szPath);if(IDOK==MessageBox(str,"提醒",MB_OKCANCEL | MB_ICONQUESTION)){bool isSuc=fileWatcher->runNewWatch(szPath);if(isSuc){HANDLE hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);handMap[szPath]=hEvent;CString sDir;m_watchDir.GetWindowText(sDir);sDir+=szPath;sDir+=";";m_watchDir.SetWindowText(sDir);MessageBox("监控成功!");}}else{MessageBox("目录选择有误!");}}
2. 因为需要监控多个目录,所以采用多线程.
函数:CreateThread
HANDLE WINAPICreateThread(LPSECURITY_ATTRIBUTESlpThreadAttributes,//安全性.一般设置为NULLSIZE_TdwStackSize,//初试栈大小,一般设置为0.,使用与调用该函数的线程相同大小的栈空间LPTHREAD_START_ROUTINElpStartAddress,//线程函数入口地址.函数因为全局函数或类静态函数.LPVOIDlpParameter,//线程函数参数DWORDdwCreationFlags,//控制线程创建的附加标志.如果是CREATE_SUSPENDED表示线程创建后为暂停状态.如果是0,创建后立即运行LPDWORDlpThreadId//接收线程ID);3. 目录监控:
这部分主要实现在线程函数里面.用户选择一个监控目录,系统都会创建一个线程去监控变化.在使用ReadDirectoryChangesW函数前,需要使用CreateFile函数.在msdn里面提到.目录需要用FILE_LIST_DIRECTORY打开.
HANDLE WINAPI CreateFile( __in LPCTSTR lpFileName,//创建或打开的文件名 __in DWORD dwDesiredAccess,//对象访问方式 __in DWORD dwShareMode,//共享方式 __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,//安全性 __in DWORD dwCreationDisposition,//如何创建文件 __in DWORD dwFlagsAndAttributes,//文件属性和标志 __in_opt HANDLE hTemplateFile//模版文件句柄);HANDLE hDir;hDir=CreateFile(watchDir,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);if(INVALID_HANDLE_VALUE==hDir) return false;4. ReadDirectoryChangesW函数实现监控.具体见代码.
char *buf=new char[DeFileWaterBuffer];FILE_NOTIFY_INFORMATION *pNotify=(FILE_NOTIFY_INFORMATION *)buf;monitorParam *mp=new monitorParam;DWORD BytesReturned;while(dirThreadMap[watchDir]){memset(buf,0,DeFileWaterBuffer);BytesReturned=0;if(ReadDirectoryChangesW(hDir,pNotify,DeFileWaterBuffer,true,FILE_NOTIFY_CHANGE_FILE_NAME |FILE_NOTIFY_CHANGE_DIR_NAME |FILE_NOTIFY_CHANGE_ATTRIBUTES |FILE_NOTIFY_CHANGE_SIZE|FILE_NOTIFY_CHANGE_LAST_WRITE|FILE_NOTIFY_CHANGE_LAST_ACCESS|FILE_NOTIFY_CHANGE_CREATION|FILE_NOTIFY_CHANGE_SECURITY,&BytesReturned,NULL,NULL)){if(BytesReturned>0){while(true){char szPath[MAX_PATH];memset(szPath,0,MAX_PATH);WideCharToMultiByte(CP_ACP,0,pNotify->FileName,pNotify->FileNameLength/2,szPath,MAX_PATH,NULL,NULL);CString sInfo;CString lastInfo;CString path=watchDir+"\\"+szPath;switch(pNotify->Action){case FILE_ACTION_ADDED:sInfo.Format("添加 %s",szPath);if(sInfo!=lastInfo){mp->sPath=path;mp->sReason="添加";mp->sTime=CMeans::getNowTime();::SendMessage(parentHwnd,UM_THREADMONITOR,0,(LPARAM)mp);lastInfo=sInfo;}break;case FILE_ACTION_REMOVED:sInfo.Format("删除 %s",szPath);if(sInfo!=lastInfo){mp->sPath=path;mp->sReason="删除";mp->sTime=CMeans::getNowTime();::SendMessage(parentHwnd,UM_THREADMONITOR,0,(LPARAM)mp);lastInfo=sInfo;}break;case FILE_ACTION_MODIFIED:sInfo.Format("修改 %s",szPath);if(sInfo!=lastInfo){mp->sPath=path;mp->sReason="修改";mp->sTime=CMeans::getNowTime();::SendMessage(parentHwnd,UM_THREADMONITOR,0,(LPARAM)mp);lastInfo=sInfo;}break;case FILE_ACTION_RENAMED_OLD_NAME:sInfo.Format("更名前 %s",szPath);if(sInfo!=lastInfo){mp->sPath=path;mp->sReason="更名前";mp->sTime=CMeans::getNowTime();::SendMessage(parentHwnd,UM_THREADMONITOR,0,(LPARAM)mp);lastInfo=sInfo;}break;case FILE_ACTION_RENAMED_NEW_NAME:sInfo.Format("更名后 %s",szPath);if(sInfo!=lastInfo){mp->sPath=path;mp->sReason="更名后";mp->sTime=CMeans::getNowTime();::SendMessage(parentHwnd,UM_THREADMONITOR,0,(LPARAM)mp);lastInfo=sInfo;}break;}if(pNotify->NextEntryOffset){pNotify=(PFILE_NOTIFY_INFORMATION)((char*)pNotify+pNotify->NextEntryOffset);}elsebreak;}}else{AfxMessageBox("存储区溢出!");}}else{DWORD dw=GetLastError();CString str;str.Format("错误码:%d",dw);AfxMessageBox(str);break;}}AfxMessageBox("退出监控!");delete[] buf;delete mp;SetEvent(handMap[watchDir]);CloseHandle(handMap[watchDir]);handMap[watchDir]=NULL;CloseHandle(hDir);
遇到的问题:
目录监控功能实现并不难.就那几个API.在这次做目录监控时,遇到的问题才让我学到了不少东西.
1、 线程函数使用了CString,线程非正常退出.CString将会导致内存泄露!
DWORD WINAPI watchThreadProc(LPVOID lpParam)//线程函数{ CString str; While(1) {}}首先定义了一个线程函数watchThreadProc.里面很简单.就定义了CString对象,然后一个死循环.主窗体启动时,创建一个线程.当主窗体退出时,子线程强制退出.此时必然内存泄露.
按理说局部变量内存分配是在栈上,生命期结束后会自动释放内存,不会造成内存泄露.而CString比较特殊,它是从进程堆(在ATL上)或CRT堆(在MFC中)分配内存.既然是在堆上面分配的内存,则必然需要释放.正常情况下,该变量生命期结束后,有管理器回收.当线程非正常退出后,无法释放CString所分配的内存,必然造成内存泄露.自然而然,当主窗体退出时,就得想办法让运行中的线程正常退出.
1、 主线程与子线程同步问题
子线程监控目录变化,之前是用的while(true),改为了while(bool变量).但是bool变量又不能事先定义,我不知道用户会创建几个线程.这时想到用map<CString,BOOL>(这里补充一点,因为将map定义为全局变量.而且多个函数中会调用,多个函数有可能在多个cpp文件中.所以用extern map<CString,BOOL>声明在需要用的.h文件中,然后定义map在一个cpp文件中.如果你直接定义map在.h文件中,会导致链接错误.)map是个好东西,用起来挺顺手.我喜欢.每次创建一个线程成功后,用路径szPath做键,TRUE做路径对应的值.如果结束某个目录的监听,就可以将对应的BOOL设置为FALSE.当关闭主窗体时,将所有监听目录的BOOL值设置为FALSE,然后退出.
但是这样有时候依然会导致内存泄露.因为主线程退出速度比较快,主线程完全退出后,子线程还没有运行完毕,这样也会导致子线程非正常退出.所以想到了线程之间的同步.主线程等待子线程完全退出后才退出.
线程同步有三种方法.互斥对象、事件对象、关键代码段.
我选择事件对象.用CreateEvent创建.
HANDLE WINAPI CreateEvent( __in_opt LPSECURITY_ATTRIBUTES lpEventAttributes,//安全性. __in BOOL bManualReset,//创建人工重置对象,还是自动重置对象 __in BOOL bInitialState,//初试状态是否为有信号状态 __in_opt LPCTSTR lpName//事件对象名称.);每当线程创建成功,创建一个事件对象,初始为无信号状态.当线程结束时,将该事件对象设置为有信号状态.主线程等待所有的事件对象为有限状态后退出.等待单个内核对象是否为有效状态,用WatiForSingleObject函数.等到多个用WaitForMultipleObjects函数.按照这种思路实现出来,一运行当点击关闭按钮后,主程序死了.刚开始以为写的代码有误,仔细检查后发现没错.后来百度谷歌才明白.windows是基于消息系统的,很多操作都是靠消息完成的,但主线程挂起以后,自然而然无法处理消息.而子线程中需要主线程处理消息后才能返回.这样就导致了死锁.
为了避免死锁,放弃使用WaitForMultipleObjects函数,该用MsgWaitForMultipleObjects函数.这个函数的特点是不仅可以等待内核对象,也可以等待消息.当有消息到达也可以返回,这样就可以处理消息,避免死锁了.
DWORD WINAPI MsgWaitForMultipleObjects( __in DWORD nCount,//要等待的内核对象个数 __in const HANDLE *pHandles,//要等待的内核对象句柄数组指针 __in BOOL bWaitAll,//true表示当所等待的内核对象全部有效是才返回,false表示只要有一个对象有效就返回.或者有消息到达时也返回. __in DWORD dwMilliseconds,//等到时间.毫秒级.INFINITE表示无限等待 __in DWORD dwWakeMask//表示等待的对象类型.一般设置QS_ALLEVENTS表示等待任何类型);
这里需要提到的是函数的返回值.
返回值 意义 WAIT_OBJECT_0+nCount有消息到达WAIT_OBJECT_0~WAIT_OBJECT_0+
nCount-1
如果bWaitAll为true表示所有等待的对象为有效状态.
如果bWaitAll为false,表示某一个对象为有效状态.
可以用函数返回值减去WAIT_OBJECT_0得到该对象在数组中的位置
WAIT_TIMEOUT等待超时WAIT_FAILED函数出错.可用GetLastError得到错误码
while(1){dRet=MsgWaitForMultipleObjects(nWaitCount,hEvent,FALSE,10000,QS_ALLINPUT);if(dRet==WAIT_OBJECT_0+nWaitCount){TRACE("收到消息,函数返回值%d\n",dRet);while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)){TranslateMessage(&msg);DispatchMessage(&msg);}}elseif(dRet>=WAIT_OBJECT_0&&dRet<WAIT_OBJECT_0+nWaitCount){nExitThreadCount++;if(nExitThreadCount<avaSize){TRACE("一个线程退出了\n");int nIndex=dRet-WAIT_OBJECT_0;hEvent[nIndex]=hEvent[nWaitCount-1];hEvent[nWaitCount-1]=NULL;--nWaitCount;}else{TRACE("所有的线程都退出!\n");break;}}}
3.关于子线程与主线程通信的问题.就像这次做的目录监控一样,子线程监控目录变化,并将变化信息显示在主窗体的CListCtrl上.之前用的是主窗体this指针传入子线程函数,然后回调主窗体方法.不过这样做有时候是有问题的.比如说我子线程监控到目录变化,我想创建一个窗体.这时候必然出错.解决方法就是采用消息机制.你自定义消息.采用SendMessage或者PostMessage将消息发送给主线程.让主线程创建窗体.
- 编写简易目录监控的心得
- 简易的监控系统
- 简易计算器的编写
- 【web安全】监控文件目录变化 , 防篡改系统的简易实现
- 如何使用inotify编写目录监控程序
- 编写Makefile的心得
- 编写openMP的心得
- 网络编程之编写简易监控网卡流量
- 完成 简易通讯录 的编写
- java编写的简易计算器
- js编写的简易计算器
- Java 编写的简易画图板
- java编写简易的画图板
- java编写的简易计算器
- python编写的简易爬虫
- wxWidgets编写的简易计算器
- Javascript编写的简易计算器
- tornado----编写简易的服务器
- 未出现的子串[unapeared.pas/c/cpp][重点]
- xml
- eclipse中的注释
- 在程序中想要看到程序入口的方法
- 2011-8-13 16:32:05
- 编写简易目录监控的心得
- fread-fwrite array
- 编程经典收藏(Java篇)
- BASH系列之三if语句
- BASH系列之四比较和判断语句
- NO.66 AR Tools-开发人员常用小工具,总有一款适合你
- POJ 3340
- 2011-8-13 16:34:40
- MySQL Replication, 主从和双主配置