C语言钩子函数示例-让程序灵动起来

本文介绍C语言中钩子函数的应用,通过函数指针提高程序执行效率,特别是在处理大量业务类型时,钩子函数能显著减少代码复杂度,提升代码可读性和维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学过C语言的同僚或其他读者,大都应该对其指针的概念感到比较敬畏

有人学完C语言后,指针使用的炉火纯青,当然也有同学此后的程序再没用过指针来做

这并不是说没使用指针的程序不是好程序,不过指针确实是使得C语言之所以是C语言的重要原因之一

本文并不直接再叙述一遍指针的概念,而是向各位介绍C语言指针的一种妙用——钩子函数

对于追求执行效率的中大型程序而言,一段程序即使能少执行一条简单的代码,也可能得到一定的性能提升

这种软件程序中,经常存在需要按数据类型、业务类型定位处理的函数,这个定位速度如果能提高

整个程序的执行速度也会变快(这里是理论上的,并不是想说肉眼可见的变快)

钩子函数本质其实就是灵活的使用函数指针,我们先看看不用钩子函数时一段程序一般怎么写:

(需求:输入两个整数和运算法则编号,输出该法则下两个数的计算结果)

#include <stdio.h>

#define OPER_TYPE_ADD   0   /* 加法 */
#define OPER_TYPE_MINUS 1   /* 减法 */
#define OPER_TYPE_MULTI 2   /* 乘法 */

int add(int a, int b) { return a + b; }
int minus(int a, int b) { return a - b; }
int multi(int a, int b) { return a * b; }

/***************** Main程序 ********************/
int main(void)
{
	int x, y, oper;
	scanf("%d %d %d", &x, &y, &oper);
	switch(oper)
	{
		case OPER_TYPE_ADD:
			printf("result = %d\n", add(x, y));
			break;
		case OPER_TYPE_MINUS:
			printf("result = %d\n", minus(x, y));
			break;
		case OPER_TYPE_MULTI:
			printf("result = %d\n", multi(x, y));
			break;
		defult:
			printf("Null oper\n");
	}
	return 0;
}

嘛,这儿我用的switch,当然if-else也没问题,不过这两种流程设计的话在这种业务类型很少的时候还OK

对于实际生产中的可能有几十上百种业务或者数据类型的时候,再这样做,程序的执行顺序就是:

(1)switch结构:从头开始挨个比对oper,直到匹配到oper再开始执行代码

(2)if-else结构:各个if-else分支的逻辑表达式挨个计算挨个比较,直到逻辑成立再执行程序

假设有100种计算法则,上述实现最糟糕的情况是每次都在第100次比对才执行,当计算请求很大量很频繁时

这段程序的执行效率就显得不是那么高了

下面我们看钩子函数怎么处理这个需求:

#include <stdio.h>

int add(int a, int b) { return a + b; }
int minus(int a, int b) { return a - b; }
int multi(int a, int b) { return a * b; }

/* 注册消息 */
enum{
	OPER_TYPE_ADD = 0,   /* 加法 */
	OPER_TYPE_MINUS,     /* 减法 */
	OPER_TYPE_MULTI,     /* 乘法 */
	OPER_TYPE_DEV,	     /* 除法 */
	OPER_TYPE_MAX
};

typedef struct handle_cb{
	int type;
	int (*handle)(int a, int b);
}HANDLE_CB;

/* 
注册钩子,这里显式地把消息类型和钩子函数对应关系体现出来了,
handle_cb里面的type可以不要的,注意维护好注册消息表中
每个枚举值先后顺序和下面钩子函数表中的先后顺序一致,能对应上
就行了 
*/
HANDLE_CB g_handle [] = {
	{ OPER_TYPE_ADD, add },
	{ OPER_TYPE_MINUS, minus },
	{ OPER_TYPE_MULTI, multi },
	{ OPER_TYPE_DEV, NULL}
};

/***************** Main程序 ********************/
int main(void)
{
	int i; 
	int type;
	int x, y, oper;
	int (*func)(int x, int y);
	scanf("%d %d %d", &x, &y, &oper);
	
	/* 
	这就是挂钩子的过程,如果回调消息类型很多,
	可以用type来定位g_handle中注册的钩子函数,
	钩子函数好处在于是提供一个接口,公共平台部分代码
	不用关系接口内实现,要做新功能的时候直接注册
	消息类型和回调函数即可,不用大动公共部分 
	*/
	func = g_handle[oper].handle;
	
	printf("result = %d\n", func(x, y));
	
	/*
	同理,一则消息需要多种处理时可以用for循环 
	*/
	func = NULL;
	printf("\n");
	for (i = 0; i < OPER_TYPE_MAX; i++)
	{
		func = g_handle[i].handle;
		if (func != NULL)
		{
			printf("result = %d\n", func(x, y));
		}
	}
	return 0;
}

对代码敏感的话你已经看出区别来了,钩子函数在确定业务类型后就直接能确定处理程序

无论需求中的业务和数据类型有多少种,只要类型能确定,就能按数组角标找到对应的钩子

在大量和频繁的计算请求到来时,每个处理都不需要和其他类型比对,只执行一次就能正确处理该类型

从代码维护和迭代的角度看,这种代码设计方式也是很合理的

在新增业务类型时,直接在全局注册表中新增enum和钩子函数指针即可

如果一个类型暂时没有处理但该类型必须有,我们把钩子置为NULL即可

一则是代码量不会像switch和if-else那样膨胀式增长,二则代码简练,可读性好,执行效率高

以下这种把函数指针定义为类型来处理的方式也是钩子函数的一种应用:

#include <stdio.h>

/* 用typedef定义以后,可以当变量名一样自定义其他函数名字 */
typedef int(*FUNC)(int x, int y);

/* 两个钩子函数 */
int add(int a, int b) { return a + b; }
int minus(int a, int b) { return a - b; }

int main(void)
{
	FUNC p;   /* p可以理解为钩子,挂在哪个函数上就实现哪种流程 */
	
	/* 以下两个流程就是同一接口调用两种实现 */
	p = add;
	printf("%d + %d = %d\n", 100, 211, (*p)(100, 211));
	p = minus;
	printf("%d - %d = %d\n", 100, 67, (*p)(100, 67));
	return 0;
} 

如果你对UNIX/Linux系统的源码实现有一定了解,就会见到这种钩子函数的实现

函数指针带来的灵活性赋予了C语言很大的生机

在C语言实现各个经典的设计模式的时候基本都会用到钩子函数的技巧

 

三:程序的设计: I:设置钩子 设置钩子是通过SetWindowsHookEx ()的API函数. 原形: HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId) idhook:装入钩子的类型. lpfn: 钩子进程的入口地址 hMod: 应用程序的事件句柄 dwThreadId: 装入钩子的线程标示 参数: idHook: 这个参数可以是以下值: WH_CALLWNDPROC、WH_CALLWNDPROCRET、WH_CBT、WH_DEBUG、WH_FOREGROUNDIDLE、WH_GETMESSAGE、WH_JOURNALPLAYBACK、WH_JOURNALRECORD、WH_KEYBOARD、 WH_KEYBOARD_LL、WH_MOUSE、WH_MOUSE_LL、WH_MSGFILTER、WH_SHELL、WH_SYSMSGFILTER。 对于这些参数,我不想一一加以解释,因为MSDN中有关于他们的详细注解。我只挑选其中的几个加以中文说明。 WH_KEYBOARD:一旦有键盘敲打消息(键盘的按下、键盘的弹起),在这个消息被放在应用程序的消息队列前,WINDOWS将会调用你的钩子函数钩子函数可以 改变和丢弃键盘敲打消息。 WH_MOUSE:每个鼠标消息在被放在应用程序的消息队列前,WINDOWS将会调用你的钩子函数钩子函数可以改变和丢弃鼠标消息。 WH_GETMESSAGE:每次当你的应用程序调用一个GetMessage()或者一个PeekMessage()为了去从应用程序的消息队列中要求一个消息时,WINDOWS都会调用你的钩子函数。 而钩子函数可以改变和丢弃这个消息。 II:释放钩子 钩子的释放使用的是UnhookWindowsHookEx()函数 原形:BOOL UnhookWindowsHookEx( HHOOK hhk ) UnhookWindowsHookEx()函数将释放的是钩子链中函数SetWindowsHookEx所装入的钩子进程。 hhk: 将要释放的钩子进程的句柄。 III:钩子进程 钩子进程使用函数HookProc;其实HookProc仅仅只是应用程序定义的符号。比如你可以写成KeyBoardHook.但是参数是不变的。Win32 API提供了诸如:CallWndProc、 GetMsgProc、DebugProc、CBTProc、MouseProc、KeyboardProc、MessageProc等函数,对于他们的详细讲解,可以看MSDN我在此只讲解一下KeyBoardHook的含义。 原形:LRESULT CALLBACK KeyBoardHook (int nCode, WPARAM wParam, LPARAM lParam) 说明:钩子进程是一些依附在一个钩子上的一些函数,因此钩子进程只被WINDOWS调用而不被应用程序调用,他们有时就需要作为一个回调函数(CALLBACK)。 参数说明: nCode:钩子代码,钩子进程使用钩子代码去决定是否执行。而钩子代码的值是依靠钩子的种类来定的。每种钩子种类都有他们自己一系列特性的代码。比如对于WH_KEYBOARD, 钩子代码的参数有:HC_ACTION,HC_NOREMOVE。HC_ACTION的意义:参数wParam 和lParam 包含了键盘敲打消息的信息,HC_NOREMOVE的意义:参数wParam 和lParam包含了 键盘敲打消息的信息,并且,键盘敲打消息一直没有从消息队列中删除。(应用程序调用PeekMessage函数,并且设置PM_NOREMOVE标志)。也就是说当nCode等于HC_ACTION时, 钩子进程必须处理消息。而为HC_NOREMOVE时,钩子进程必须传递消息给CallNextHookEx函数,而不能做进一步的处理,而且必须有CallNextHookEx函数的返回值。 wParam:键盘敲打所产生的键盘消息,键盘按键的虚拟代码。 lParam:包含了消息细节。 注意:如果钩子进程中nCode小于零,钩子进程必须返回(return) CallNextHookEx(nCode,wParam,lParam);而钩子进程中的nCode大于零,但是钩子进程并不处理消息, 作者推荐你调用CallNextHookEx并且返回该函数的返回值。否则,如果另一个应用程序也装入WH_KEYBOARD 钩子,那么该钩子将不接受钩子通知并且返回一个不正确的值。 如果钩子进程处理了消息,它可能返回一个非零值去阻止系统传递该信息到其它剩下的钩子或者windows进程。所以最好在钩子进程的最后都返回CallNextHookEx的返回
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值