函数时编程——钩子(Hook)函数

【百度百科】钩子函数是Windows消息处理机制的一部分,通过设置“钩子”,应用程序可以在系统级对所有消息、事件进行过滤,访问在正常情况下无法访问的消息。钩子的本质是一段用以处理系统消息的程序,通过系统调用,把它挂入系统。

系统范围的将捕捉系统中所有进程将发生的事件消息。 当您创建一个钩子时,WINDOWS会先在内存中创建一个数据结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去。新的钩子将加到老的前面。当一个事件发生时,如果您安装的是一个局部钩子,您进程中的钩子函数将被调用。如果是一个远程钩子,系统就必须把钩子函数插入到其它进程的地址空间,要做到这一点要求钩子函数必须在一个动态链接库中,所以如果您想要使用远程钩子,就必须把该钩子函数放到动态链接库中去。

钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。对每种类型的钩子由系统来维护一个钩子链,最近安装的钩子放在链的开始,而最先安装的钩子放在最后,也就是后加入的先获得控制权。

从某种程度上讲,钩子是一系列被设计为以你自己的代码来处理自定义值的回调函数。有了钩子,你可以将差不多任何东西保持在可控范围内。

钩子函数和回调函数

回调函数其实就是调用者把回调函数的函数指针传递给调用函数,当调用函数执行完毕时,通过函数指针来调用回调函数。
他们都是为了捕获消息而生的,区别在于钩子函数在捕获消息的第一时间就会执行,而回调函数是在整个捕获过程结束后才执行的。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// hook function
Function.prototype.hook = function (hookFunc, context) {
if (this.prototype && this.prototype.isHooked) {
console.warn(`Warning! Function ${this.name} has been hooked.`);
return false;
}

context = context || window;
let _name = this.name;
try {
let _cb = context[_name];
context[_name] = function () {
var args = Array.prototype.slice.call(arguments, 0);
hookFunc.apply(this, args);
return _cb.apply(this, args);
};
return true;
} catch (e) {
console.error(`Failed! Function ${this.name} hook failed,check the params.`, e);
return false;
}
}

// unhook function
Function.prototype.unhook = function (context) {
if (!this.prototype.isHooked) {
console.warn(`Warning! No function is hooked on.`);
return false;
}

delete context[this.name];
return true;
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
function test (num1, num2, num3) { 
console.log(`function test.Params: ${num1}/${num2}/${num3}`);
}
function hookfunc (num1, num2, num3) {
console.log(`function hookfunc.Params: ${num1}/${num2}/${num3}`);
}

test.hook(hookfunc);

test(1, 2, 3);

// function hookfunc.Params: 1/2/3
// function test.Params: 1/2/3