文章目录
- 前言
- 解决方案
- 那就开始实现吧
- 底层API接口
- 如何使用
- 准备工作
- 开始监听
- 最最最简单的示例main.cpp代码
- 把功能嵌入你的项目
- 最后附源码以及编译好的示例链接
前言
很多时候,我们想获取Windows的所有正在进行的文件操作情况。比如想知道某些网页视频的缓存文件位置、某些软件的缓存路径;想知道杀毒软件到底删了什么东西;在实现一个类似网盘拖拽下载功能的时候,想知道用户把鼠标拖拽到了哪个文件夹(因为鼠标已经拖出你的应用外了,在应用里是不能直接获取到的)。等等。
解决方案
很简单粗暴,直接监听Windows的文件读写(不要希望用文件搜索,隔一段时间搜索一次,保存之前的所有文件状态,然后下次搜索时和之前的比较,不要希望这样能解决问题,因为效率极低,单单全盘搜索一次估计就要几十分钟了,更别谈比较两次搜索获取变化了的文件了)。监听的方法很简单,就是开启一个文件钩子,在Windows系统发生文件操作的时候,让Windows主动调用你的回调函数。这种方法可以做到实时监听文件操作状态,效率极高的,原因是在Windows进行文件操作的时候,它自己肯定是知道是哪个文件正在做什么操作的,它只需要这个时候回调一下你的函数,通过参数告诉你哪个文件正在做什么操作就可以了,时间复杂度是O(1)(任何情况下都是),和之前的全盘文件搜索(假设你的电脑里有n个文件)然后再和之前的对比,时间复杂度是O(n)(最好情况下,如果之后文件发生了翻天覆地的变化,要匹配就更麻烦了)相比显得很离谱吧。
那就开始实现吧
底层API接口
微软有给Windows写文件钩子的接口,但是要实现起来挺麻烦,所以我就在网上查已经实现了的文件钩子源码,然后复制粘贴 学习借鉴。
找了两三天后,终于给我找到了一个可以用的源码(源码在下方链接有,因为过得太久才写的这篇博客,忘记源码在哪找的了,所以直接放github上了。稍微改动过,把一些写日志的代码都注释了,然后写日志需要的头文件也没放上去)。
如何使用
准备工作
刚刚下载到这个源码的时候我也很懵,因为没有文档(我不知道它是不是有文档,反正单单是找到这个源码就花了我很长时间了)。在这写下这个源码的使用方法。
首先把源码添加到工程里(WatchData.h WatchData.cpp Win32FSHook.h Win32FSHook.cpp四个都要,在最下方有源码链接)
添加好源码之后,在你的代码里添加两个类型转换的函数:
//因为文件钩子回调的参数是wchar_t *类型,为了让你的程序正常获取到参数的意思,要转成char *类型
//说明:待转换的字符串是wchar,返回值保存在m_char开辟的空间里
char *wchar2char(const wchar_t *wchar, char *m_char)
{
int len = WideCharToMultiByte(CP_ACP, 0, wchar, wcslen(wchar), NULL, 0, NULL, NULL);
WideCharToMultiByte(CP_ACP, 0, wchar, wcslen(wchar), m_char, len, NULL, NULL);
m_char[len] = '\0';
return m_char;
}
//因为添加文件钩子监听目标文件夹的时候,要用wchar_t *类型传参,为了方便,直接写个char *到wchar_t *的转换函数
//说明:待转换的字符串是cchar,返回值保存在m_wchar开辟的空间里
wchar_t *char2wchar(const char *cchar, wchar_t *m_wchar)
{
int len = MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), NULL, 0);
MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), m_wchar, len);
m_wchar[len] = '\0';
return m_wchar;
}
开始监听
首先写一个回调函数:
//watchID是钩子监听的id,是在Win32FSHook::add_watch时的返回值
//action是返回的文件名正在进行的操作的类型:
// 1是文件创建,2是文件删除,3是复制,4是重命名的原文件名,5是重命名的新文件名,等。
void win32FSCallback(int watchID, int action, const WCHAR* rootPath, const WCHAR* filePath)
{
char tmp[256]; //windows系统文件名只需要256个字节就可以了
char filename[256];
strcpy(filename, wchar2char(rootPath, tmp));
strcat(filename, wchar2char(filePath, tmp)); //把根目录和相对根目录的文件名拼起来就是完整的文件名了
//std::cout<<"watchID: "<<watchID<<std::endl;
switch(action)
{
case 1:
{
std::cout<<"创建:"<<filename<<std::endl;
break;
}
case 2:
{
std::cout<<"删除:"<<filename<<std::endl;
break;
}
case 3:
{
std::cout<<"复制:"<<filename<<std::endl;
break;
}
case 4: //在4时别急着换行,因为windows在回调了4之后,就会回调5,拼起来意思才完整
{
std::cout<<"重命名原文件名:"<<filename;
break;
}
case 5:
{
std::cout<<",新文件名:"<<filename<<std::endl;
break;
}
default: //其他的一些操作,因为没有文档,我也不知道还有没有其他的action
{
std::cout<<"操作代码'"<<action<<"':"<<filename<<std::endl;
}
}
}
然后是添加和移除监听路径:
//----如果后续可能需要移除监听路径要有个全局变量----
int watchID;
//--------------------------------------------
//--------------添加监听路径的代码段-------------
Win32FSHook win32FSHook;
//win32FSCallback就是回调函数的函数名,当有满足条件的文件操作时windows会回调win32FSCallback
win32FSHook.init(win32FSCallback);
DWORD err;
wchar_t path[256];
//这个方法可以多次调用,添加多个监听路径,
//其中第一个参数为监听的根路径,C:\\表示监听C盘下的所有文件,
//第二个参数为监听的操作类型:
// 1为监听文件创建 (b0001),
// 2为监听文件删除 (b0010),
// 4为监听文件被修改 (b0100),
// 8为监听文件重命名 (b1000)。
//可以使用|监听多个操作类型
//第三个参数为是否监听子文件夹,
//第四个参数是用来获取失败信息的,2表示监听失败
watchID = win32FSHook.add_watch(char2wchar("C:\\", path),
1|2|4|8,
true,
err);
if(err == 2)
{
std::cout<<"该目录无法监听"<<std::endl;
}
//---------------------------------------------
//--------------移除监听路径的代码段--------------
win32FSHook.remove_watch(watchID); //没错watchID的作用在这
//---------------------------------------------
最最最简单的示例main.cpp代码
#include <iostream>
#include <string.h>
#include <windows.h>
#include "Win32FSHook.h"
char *wchar2char(const wchar_t *wchar, char *m_char)
{
int len = WideCharToMultiByte(CP_ACP, 0, wchar, wcslen(wchar), NULL, 0, NULL, NULL);
WideCharToMultiByte(CP_ACP, 0, wchar, wcslen(wchar), m_char, len, NULL, NULL);
m_char[len] = '\0';
return m_char;
}
wchar_t *char2wchar(const char *cchar, wchar_t *m_wchar)
{
int len = MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), NULL, 0);
MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), m_wchar, len);
m_wchar[len] = '\0';
return m_wchar;
}
void win32FSCallback(int watchID, int action, const WCHAR* rootPath, const WCHAR* filePath)
{
char tmp[256]; //windows系统文件名只需要256个字节就可以了
char filename[256];
strcpy(filename, wchar2char(rootPath, tmp));
strcat(filename, wchar2char(filePath, tmp)); //把根目录和相对根目录的文件名拼起来就是完整的文件名了
//std::cout<<"watchID: "<<watchID<<std::endl;
switch(action)
{
case 1:
{
std::cout<<"创建:"<<filename<<std::endl;
break;
}
case 2:
{
std::cout<<"删除:"<<filename<<std::endl;
break;
}
case 3:
{
std::cout<<"复制:"<<filename<<std::endl;
break;
}
case 4: //在4时别急着换行,因为windows在回调了4之后,就会回调5,拼起来意思才完整
{
std::cout<<"重命名原文件名:"<<filename;
break;
}
case 5:
{
std::cout<<",新文件名:"<<filename<<std::endl;
break;
}
default: //其他的一些操作,因为没有文档,我也不知道还有没有其他的action
{
std::cout<<"操作代码'"<<action<<"':"<<filename<<std::endl;
}
}
}
int main()
{
int watchID;
Win32FSHook win32FSHook;
win32FSHook.init(win32FSCallback);
DWORD err;
wchar_t path[256];
watchID = win32FSHook.add_watch(char2wchar("C:\\", path),
1|2|4|8,
true,
err);
if(err == 2)
{
std::cout<<"该目录无法监听"<<std::endl;
}
else
{
std::cout<<"开始监听,按下回车结束监听"<<std::endl;
getchar();
win32FSHook.remove_watch(watchID);
std::cout<<"结束监听,按下回车退出程序"<<std::endl;
getchar();
}
return 0;
}
把功能嵌入你的项目
在win32FSCallback里可以做很多文章,获取到文件名后你可以做任何你想做的事情。但是记住:千万不要在win32FSCallback里写文件操作,特别是还在监听目录内的文件操作,因为很容易造成死循环(刚操作完,回调你的函数后,你在函数里又文件操作,那windows是不是又要回调了,然后就死循环了)
假设你的需求是实现网盘拖拽下载,可以在鼠标拖拽绑定一个文件复制事件,把一个已知文件路径的文件url放在MimeData里(这个文件的最后一级文件名最好是唯一的,要不然可能出现下载位置错误的情况),然后松开鼠标后执行复制的Action,文件钩子就可以获取到文件复制操作,然后根据对比获取到执行复制操作的文件的最后一级文件名和之前已知的文件最后一级文件名是否一样,就能确定拖拽文件的下载路径了。
最后附源码以及编译好的示例链接
https://github/Eyre-Turing/file_hook
文章目录
- 前言
- 解决方案
- 那就开始实现吧
- 底层API接口
- 如何使用
- 准备工作
- 开始监听
- 最最最简单的示例main.cpp代码
- 把功能嵌入你的项目
- 最后附源码以及编译好的示例链接
前言
很多时候,我们想获取Windows的所有正在进行的文件操作情况。比如想知道某些网页视频的缓存文件位置、某些软件的缓存路径;想知道杀毒软件到底删了什么东西;在实现一个类似网盘拖拽下载功能的时候,想知道用户把鼠标拖拽到了哪个文件夹(因为鼠标已经拖出你的应用外了,在应用里是不能直接获取到的)。等等。
解决方案
很简单粗暴,直接监听Windows的文件读写(不要希望用文件搜索,隔一段时间搜索一次,保存之前的所有文件状态,然后下次搜索时和之前的比较,不要希望这样能解决问题,因为效率极低,单单全盘搜索一次估计就要几十分钟了,更别谈比较两次搜索获取变化了的文件了)。监听的方法很简单,就是开启一个文件钩子,在Windows系统发生文件操作的时候,让Windows主动调用你的回调函数。这种方法可以做到实时监听文件操作状态,效率极高的,原因是在Windows进行文件操作的时候,它自己肯定是知道是哪个文件正在做什么操作的,它只需要这个时候回调一下你的函数,通过参数告诉你哪个文件正在做什么操作就可以了,时间复杂度是O(1)(任何情况下都是),和之前的全盘文件搜索(假设你的电脑里有n个文件)然后再和之前的对比,时间复杂度是O(n)(最好情况下,如果之后文件发生了翻天覆地的变化,要匹配就更麻烦了)相比显得很离谱吧。
那就开始实现吧
底层API接口
微软有给Windows写文件钩子的接口,但是要实现起来挺麻烦,所以我就在网上查已经实现了的文件钩子源码,然后复制粘贴 学习借鉴。
找了两三天后,终于给我找到了一个可以用的源码(源码在下方链接有,因为过得太久才写的这篇博客,忘记源码在哪找的了,所以直接放github上了。稍微改动过,把一些写日志的代码都注释了,然后写日志需要的头文件也没放上去)。
如何使用
准备工作
刚刚下载到这个源码的时候我也很懵,因为没有文档(我不知道它是不是有文档,反正单单是找到这个源码就花了我很长时间了)。在这写下这个源码的使用方法。
首先把源码添加到工程里(WatchData.h WatchData.cpp Win32FSHook.h Win32FSHook.cpp四个都要,在最下方有源码链接)
添加好源码之后,在你的代码里添加两个类型转换的函数:
//因为文件钩子回调的参数是wchar_t *类型,为了让你的程序正常获取到参数的意思,要转成char *类型
//说明:待转换的字符串是wchar,返回值保存在m_char开辟的空间里
char *wchar2char(const wchar_t *wchar, char *m_char)
{
int len = WideCharToMultiByte(CP_ACP, 0, wchar, wcslen(wchar), NULL, 0, NULL, NULL);
WideCharToMultiByte(CP_ACP, 0, wchar, wcslen(wchar), m_char, len, NULL, NULL);
m_char[len] = '\0';
return m_char;
}
//因为添加文件钩子监听目标文件夹的时候,要用wchar_t *类型传参,为了方便,直接写个char *到wchar_t *的转换函数
//说明:待转换的字符串是cchar,返回值保存在m_wchar开辟的空间里
wchar_t *char2wchar(const char *cchar, wchar_t *m_wchar)
{
int len = MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), NULL, 0);
MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), m_wchar, len);
m_wchar[len] = '\0';
return m_wchar;
}
开始监听
首先写一个回调函数:
//watchID是钩子监听的id,是在Win32FSHook::add_watch时的返回值
//action是返回的文件名正在进行的操作的类型:
// 1是文件创建,2是文件删除,3是复制,4是重命名的原文件名,5是重命名的新文件名,等。
void win32FSCallback(int watchID, int action, const WCHAR* rootPath, const WCHAR* filePath)
{
char tmp[256]; //windows系统文件名只需要256个字节就可以了
char filename[256];
strcpy(filename, wchar2char(rootPath, tmp));
strcat(filename, wchar2char(filePath, tmp)); //把根目录和相对根目录的文件名拼起来就是完整的文件名了
//std::cout<<"watchID: "<<watchID<<std::endl;
switch(action)
{
case 1:
{
std::cout<<"创建:"<<filename<<std::endl;
break;
}
case 2:
{
std::cout<<"删除:"<<filename<<std::endl;
break;
}
case 3:
{
std::cout<<"复制:"<<filename<<std::endl;
break;
}
case 4: //在4时别急着换行,因为windows在回调了4之后,就会回调5,拼起来意思才完整
{
std::cout<<"重命名原文件名:"<<filename;
break;
}
case 5:
{
std::cout<<",新文件名:"<<filename<<std::endl;
break;
}
default: //其他的一些操作,因为没有文档,我也不知道还有没有其他的action
{
std::cout<<"操作代码'"<<action<<"':"<<filename<<std::endl;
}
}
}
然后是添加和移除监听路径:
//----如果后续可能需要移除监听路径要有个全局变量----
int watchID;
//--------------------------------------------
//--------------添加监听路径的代码段-------------
Win32FSHook win32FSHook;
//win32FSCallback就是回调函数的函数名,当有满足条件的文件操作时windows会回调win32FSCallback
win32FSHook.init(win32FSCallback);
DWORD err;
wchar_t path[256];
//这个方法可以多次调用,添加多个监听路径,
//其中第一个参数为监听的根路径,C:\\表示监听C盘下的所有文件,
//第二个参数为监听的操作类型:
// 1为监听文件创建 (b0001),
// 2为监听文件删除 (b0010),
// 4为监听文件被修改 (b0100),
// 8为监听文件重命名 (b1000)。
//可以使用|监听多个操作类型
//第三个参数为是否监听子文件夹,
//第四个参数是用来获取失败信息的,2表示监听失败
watchID = win32FSHook.add_watch(char2wchar("C:\\", path),
1|2|4|8,
true,
err);
if(err == 2)
{
std::cout<<"该目录无法监听"<<std::endl;
}
//---------------------------------------------
//--------------移除监听路径的代码段--------------
win32FSHook.remove_watch(watchID); //没错watchID的作用在这
//---------------------------------------------
最最最简单的示例main.cpp代码
#include <iostream>
#include <string.h>
#include <windows.h>
#include "Win32FSHook.h"
char *wchar2char(const wchar_t *wchar, char *m_char)
{
int len = WideCharToMultiByte(CP_ACP, 0, wchar, wcslen(wchar), NULL, 0, NULL, NULL);
WideCharToMultiByte(CP_ACP, 0, wchar, wcslen(wchar), m_char, len, NULL, NULL);
m_char[len] = '\0';
return m_char;
}
wchar_t *char2wchar(const char *cchar, wchar_t *m_wchar)
{
int len = MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), NULL, 0);
MultiByteToWideChar(CP_ACP, 0, cchar, strlen(cchar), m_wchar, len);
m_wchar[len] = '\0';
return m_wchar;
}
void win32FSCallback(int watchID, int action, const WCHAR* rootPath, const WCHAR* filePath)
{
char tmp[256]; //windows系统文件名只需要256个字节就可以了
char filename[256];
strcpy(filename, wchar2char(rootPath, tmp));
strcat(filename, wchar2char(filePath, tmp)); //把根目录和相对根目录的文件名拼起来就是完整的文件名了
//std::cout<<"watchID: "<<watchID<<std::endl;
switch(action)
{
case 1:
{
std::cout<<"创建:"<<filename<<std::endl;
break;
}
case 2:
{
std::cout<<"删除:"<<filename<<std::endl;
break;
}
case 3:
{
std::cout<<"复制:"<<filename<<std::endl;
break;
}
case 4: //在4时别急着换行,因为windows在回调了4之后,就会回调5,拼起来意思才完整
{
std::cout<<"重命名原文件名:"<<filename;
break;
}
case 5:
{
std::cout<<",新文件名:"<<filename<<std::endl;
break;
}
default: //其他的一些操作,因为没有文档,我也不知道还有没有其他的action
{
std::cout<<"操作代码'"<<action<<"':"<<filename<<std::endl;
}
}
}
int main()
{
int watchID;
Win32FSHook win32FSHook;
win32FSHook.init(win32FSCallback);
DWORD err;
wchar_t path[256];
watchID = win32FSHook.add_watch(char2wchar("C:\\", path),
1|2|4|8,
true,
err);
if(err == 2)
{
std::cout<<"该目录无法监听"<<std::endl;
}
else
{
std::cout<<"开始监听,按下回车结束监听"<<std::endl;
getchar();
win32FSHook.remove_watch(watchID);
std::cout<<"结束监听,按下回车退出程序"<<std::endl;
getchar();
}
return 0;
}
把功能嵌入你的项目
在win32FSCallback里可以做很多文章,获取到文件名后你可以做任何你想做的事情。但是记住:千万不要在win32FSCallback里写文件操作,特别是还在监听目录内的文件操作,因为很容易造成死循环(刚操作完,回调你的函数后,你在函数里又文件操作,那windows是不是又要回调了,然后就死循环了)
假设你的需求是实现网盘拖拽下载,可以在鼠标拖拽绑定一个文件复制事件,把一个已知文件路径的文件url放在MimeData里(这个文件的最后一级文件名最好是唯一的,要不然可能出现下载位置错误的情况),然后松开鼠标后执行复制的Action,文件钩子就可以获取到文件复制操作,然后根据对比获取到执行复制操作的文件的最后一级文件名和之前已知的文件最后一级文件名是否一样,就能确定拖拽文件的下载路径了。
最后附源码以及编译好的示例链接
https://github/Eyre-Turing/file_hook