c++ windows平台的Hook

环境:vs2017,添加Windows.h头文件。
核心函数:SetWindowsHookEx()UnhookWindowsHookEx()CallNextHookEx()
你可能在其他api文档看到SetWindowsHookExA、SetWindowsHookExW这两个函数,他们是编码上的区别,A指ASCII,W指wide-char也就是unicode编码,但系统已经帮我们用宏处理好,只需调用SetWindowsHookEx就行了,自动选择对应编码那个函数。
Hook:建议先百度自行稍微了解
HINSTANCE、HMODULE、HANDLE、HWND是一样的东西,都是各种typedef绕来绕去,区别只在于人类使用时附加的语义,实际上都是一串数字。下面就混着用了。
下面仅以最基本的控制台应用示例。



非常简单的一个例子,运行后鼠标移动即可看到效果:

LRESULT CALLBACK mymouse(int nCode, WPARAM wParam, LPARAM lParam)
{
    cout<<"yes"<<endl;
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

int main()
{
    HHOOK mouseHook = SetWindowsHookEx(WH_MOUSE_LL, mymouse, 0, 0);
    MSG msg;
    while (GetMessage(&msg, NULL, NULL, NULL))
    {
    }
    UnhookWindowsHookEx(mouseHook);
    return 0;
}

1.mymouse函数:
LRESULT:long result,实际就是个宏,当long(长整数)就行。
CALLBACK:也是宏,实际上是__stdcall,函数修饰关键字,详细自行可百度。
WPARAM、LPARAM:还是宏,当整数就行。不同的值有不同含义,具体看msdn。

为什么这样定义?
因为SetWindowsHookEx()的第二个参数就是接受这样的一个函数指针。简单了解使用函数指针
该函数返回0时,消息继续往下传递;返回1时消息不再往下传递。

为什么是return CallNextHookEx()而不是直接return 0或1?
SetWindowsHookEx()把这个钩子放在Hook链头,不调用这个方法其他链节点的Hook不执行。另外该函数的第一个参数没用,直接null就行。

2.HHOOK类型:
代表加进去的钩子,后面解除钩子的函数UnhookWindowsHookEx(),参数就是这个的对象。

3.MSG、while:
为了不让程序结束,用一个while卡住它。

为什么不用while(true)?
看第5点

4.SetWindowsHookEx函数:
原型:

HHOOK WINAPI SetWindowsHookEx(
  _In_ int       idHook,
  _In_ HOOKPROC  lpfn,
  _In_ HINSTANCE hMod,
  _In_ DWORD     dwThreadId
);

看到陌生的不用头大,都是关键字和宏罢了,直接讲4个参数。
idHook:一个int值,不同的值对应不同的钩子(鼠标or键盘之类的)。vs中打出WH_后,看自动补全显示的名字,你就能了解了,不用死记硬背。WH就是window hook的意思。
lpfn:函数指针,类型就是mymouse函数那个类型。每当钩子获取到消息,就会调用该函数指针。
hMod:用于帮助找到真正的地址,下面会讲。
dwThreadId:DWORD也是一个宏,当整数就行。这里指你要把钩子挂到哪个线程中,所有进程的所有线程都可以选择,只不过其他进程的线程不一定挂的上,需要其他手段,不作细说。填0就是尝试把所有线程都给挂上,成了所谓的全局钩子(只是尝试,拒绝挂钩子的线程还是挂不上)。

当挂钩子失败时,该函数返回null。另外msdn讲解参数这段一定要看:
lpfn [in]
Type: HOOKPROC

A pointer to the hook procedure. If the dwThreadId parameter is zero or specifies the identifier of a thread created by a different process, the lpfn parameter must point to a hook procedure in a DLL. Otherwise, lpfn can point to a hook procedure in the code associated with the current process.
如果dwThreadId指定的线程并不是由当前进程创建的,那么子程(就是那个函数,如示例的mymouse)一定要写在dll里(动态链接库)。
原因:由于lpfn是指针和逻辑地址的机制,当前进程的函数地址不适用于其他进程。系统一般需要借助dll模块以确定函数入口地址(low-level除外,只有此时可以不写dll)。

hMod [in]
Type: HINSTANCE

A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process.
当 dwThreadId指定的线程是当前进程创建的线程 子程的代码是在当前进程(非dll)写的,那么这个hMod必须填0(也就是null)。
值得一提,在dll中时,这个参数要填为该dll的句柄,可以有两种方法获取来填:
(1)把入口DllMain的参数hModule(HMODULE和HINSTANCE一样的)保存下来,填进去(推荐)
(2)通过函数GetModuleHandle(L"dll name")来获取(加L代表为unicode编码,不用加dll后缀)(不推荐)

绝大多数情况下会写成动态链接库(建议)。上面示例能算上特例,不写在dll中就要:首先是钩子类型不是WH_MOUSE而是WH_MOUSE_LL(LL代表low-level,另一种回调机制,写在dll中则两种都可以),不然没反应,其次第三个参数hMod写成0。

5.GetMessage函数:从线程消息队列获取消息,没有消息将进入等待态直至有消息被插入队列
(1)系统获取消息-----系统分发消息---(Hook链)-----消息抵达线程消息队列。(这下搞懂第一点说的消息是否继续传递和CallNextHookEx了吧)
(2)我们示例的控制台程序创建时默认是没有消息队列的,但调用相关函数时就自动创建消息队列,如GetMessage函数
(3)子程是由系统通过回调机制,把执行SetWindowsHookEx函数的线程叫回来去执行的,也就是示例中的mymouse函数是由主线程去执行的。因此,如果写成while(true),那么线程将无限运行,系统没有机制能够中断它的执行来把它叫过来执行子程。但是,调用GetMessage函数后,线程有了消息队列,而且我们没有往里面插入消息,线程就进入等待态,此时系统便能够控制该线程,让它去执行子程。也就是说,我们需要把这个线程弄成等待态去让系统获取控制权,所以你还可以使用while(true){ Sleep(100); }达到相同目的。
(4)由于上述原因,对于挂Hook的线程来说,这样一个循环查询消息队列是有必要的。
(5)如果系统发现hook的子程长期得不到执行(你的线程正在繁忙),那么系统会自动删除掉这个hook。这里就要注意了,不要安排耗时任务在该线程上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容