编写简易目录监控的心得

来源:互联网 发布:bp神经网络算法 公式 编辑:程序博客网 时间:2024/05/21 21:46
目录监控主要用WinAPI函数ReadDirectoryChangeW实现对目录中的文件进行删除,修改,重命名功能监控,并将监控信息显示在界面上.用户可以选择多个目录进行监控.

主界面如图1所示:

                               图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将消息发送给主线程.让主线程创建窗体.

 

 

原创粉丝点击