最近在做云盘,本来的同步方式效率比较低,所以我就想着做个文件夹监视,监控同步的文件夹。所以用到的ReadDirectoryChangesW这个api,虽然网上关于这个api的介绍有不少但是有些细节介绍的还是比较少的所以留下这篇文章给大家一个参考,也给自己留个备份。
首先贴下我的代码:
void CFolderMonitor::DoMonitor()
{
char *buffer = new char[1024*2];
DWORD dwBytes=0;
HANDLE hDir = ::CreateFile(
m_strMonitorDir, // 文件名的指针
FILE_LIST_DIRECTORY, // 访问(读/写)模式
FILE_SHARE_READ // 共享模式
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE,
NULL, // security descriptor
OPEN_EXISTING, // 如何创建
FILE_FLAG_BACKUP_SEMANTICS // 文件属性
| FILE_FLAG_OVERLAPPED,
NULL); // 文件属性的模板文件
while (1)
{
memset(buffer,0,1024*2);
BOOL success = ReadDirectoryChangesW(
hDir,
buffer,
1024*2,
TRUE, // monitor children?
FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_CREATION
| FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_DIR_NAME,
&dwBytes,
NULL,//&m_Overlapped,
NULL);
DWORD cbOffset = 0;
PFILE_NOTIFY_INFORMATION record = 0;
record = (PFILE_NOTIFY_INFORMATION)buffer;
do
{
char filebuffer[512] = {0};
//保证文件名的正确
WideCharToMultiByte(CP_ACP, 0, record->FileName, record->FileNameLength-1, filebuffer, record->FileNameLength-1, NULL, NULL);
BYTE *p = (BYTE *)record->FileName;
int count = 0;
for(int i=0;i<record->FileNameLength;i++)
{
if (*p != 0)
{
count++;
}
p++;
}
filebuffer[count] = '\0';
int a = sizeof(record->FileName);
CString strDir(filebuffer);
CString strPath = m_strMonitorDir+"\\"+strDir;
PostFileChange(strPath,record->Action);
cbOffset = record->NextEntryOffset;
record = (PFILE_NOTIFY_INFORMATION)((LPBYTE) record + cbOffset);
} while (cbOffset);
}
}
关于ReadDirectoryChangesW 有几点向说明下:
1.我用的是同步的方式,所以最后2个参数为null
2.程序执行到ReadDirectoryChangesW 时阻塞,等待文件夹内文件变更,一旦变更继续执行,参数buffer被填充进更改文件的信息,要获取信息要将buffer转为FILE_NOTIFY_INFORMATION结构体
3.在接收到API返回的变更信息后还要做一个do。。while循环。因为可能一手操作会有两个动作:比如更改一个文件的文件名,会产生2个文件动作FILE_ACTION_RENAMED_OLD_NAME 和 FILE_ACTION_RENAMED_NEW_NAME 但是这2个动作确是写在一个结构体内返回的,所以要对结构体中的NextEntryOffset值做一个判断,到零才跳出循环。如果不做操作,那只能获得改名前的名字,新名字无法获取。
4.我们来看一下返回的结构体
typedef struct _FILE_NOTIFY_INFORMATION {
DWORD NextEntryOffset;
DWORD Action;
DWORD FileNameLength;
WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;
我们发现文件名是WCHAR FileName[1]; 是一个宽字符的数组,但是数组大小只有1,所以我们需要FileNameLength这个变量,来将文件名转换为多字节的字符,网上有转换的api:
char filebuffer[512] = {0};
WideCharToMultiByte(CP_ACP, 0, record->FileName, record->FileNameLength, filebuffer, record->FileNameLength, NULL, NULL);
但是我用了以后发现获取的文件名有时候会带有一些小尾巴(比如‘.’,'其他英文字母',‘$’等等),其中的原因我也不清楚,但是后来我发现FileNameLength值是对的,于是我就自己用了个笨办法,现计算一遍 WCHAR FileName;中不为零的个数count,然后将获得的文件名数组的第count个的值设为'\0';问题暂时解决。