C语言钩子函数详解

目录

一:C语言功能模块规范

二:如何生成.a文件

三:注册真正功能函数

四:makefile编写

五:编译运行结果


钩子函数,从表面意思上看就不是一个名门正派,拿同事的话讲这个就是一个下三滥的手段(哈哈哈),不过对于初学者碰到钩子函数可能会有点蒙圈。正好最近又遇到了这个钩子函数,所以通过例子来详细讲解一下钩子函数,顺便也科普C语言一个完整的功能模块创建规范是什么样的。

钩子函数本质上一个函数指针。这时候讲一些钩子函数什么作用,为什么要用钩子函数,可能大部分都听不懂,所以还是通过例子来解释。我们例子要实现一个什么样的功能,我们要引用一个静态库,里面有个函数初始化了三个学生的姓名,存储在结构体里,如何通过钩子函数来将三个学生的姓名给它钩出来。

一:C语言功能模块规范

正常情况下,一个大型的C语言工程,都会分成很多的功能模块,这样做一是将功能单一的模块放在一起,利于后续维护,二是各个模块分开每个人并行开发,文件层次更加清晰。

每个C语言模块,一般都会有头文件,源文件,有的也会有静态库或者动态库文件,生成的目标文件。另外如果引用了静态库,也会有静态库的头文件。所以完整的C语言模块文件夹一般如下

[root@promote c_LEARNING]# tree HOOKMAIN/
HOOKMAIN/
├── include
│   └── hookfunc_main.h
├── library
│   ├── include
│   └── lib
│       └── libstudentsInfo.a
├── makefile
├── source
│   └── hookfunc_main.c
└── target
    └── hookfuncMain


[root@promote HOOKMAIN]# ll
total 4
drwxrwxrwx 2 root root   29 Jan 23 19:52 include
drwxrwxrwx 4 root root   32 Jan 23 19:52 library
-rwxrwxrwx 1 root root 1279 Jan 23 20:53 makefile
drwxrwxrwx 2 root root   29 Jan 23 19:51 source
drwxr-xr-x 2 root root   26 Jan 23 20:53 target

可以看到library文件夹里面又有include和lib文件夹,这就是上面提到的引用了静态库,会有.a文件和伴随静态库的.h文件

二:如何生成.a文件

什么是.a文件?.a文件就是静态库,静态库是一些.o目标文件的集合,一般以.a形式结尾。静态库在程序链接阶段使用,链接器将程序要用到的函数从库中提取出来,并整合到程序中,程序运行不再使用静态库了。由于每个程序要用到函数都从库提取并整合在一起,所以可执行文件夹会比较大。

生成.a文件有两个步骤

1,用gcc命令生成.o文件

gcc -o 可执行文件 调用者的C源文件.c  -Ldir -l库文件名

2,用ar命令组织.o文件生成.a文件

ar rcs lib库文件名.a 目标文件1 目标文件2 ... 目标文件n
  • r:表示将.o目标文件加入到静态库中;
  • c:表示创建静态库;
  • s:表示生产索引;

首先看一下我们生成.a文件的源文件students_info.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FAIL    0
#define SUCCESS 1

typedef enum{
    STUDENTS_111 = 1,
    STUDENTS_222,
    STUDENTS_333
}STUDENTS_INFO_NUM;

typedef void (*GET_SUTDENTS_NAME_INFO_Fucptr)(char *pBuf, STUDENTS_INFO_NUM sutdentsNum);

GET_SUTDENTS_NAME_INFO_Fucptr GET_SUTDENTS_NAME_INFO_HOOK=NULL;

int egRegGetStudentsInfoFunc(GET_SUTDENTS_NAME_INFO_Fucptr pGetStudentsInfoFuc)
{
    if(pGetStudentsInfoFuc == NULL)
    {
        return FAIL;
    }
    GET_SUTDENTS_NAME_INFO_HOOK = pGetStudentsInfoFuc;
    
    return SUCCESS;
}

void getStudentsInfoEntry()
{
	char *students_111_name = "KaiYing Z";
	char *students_222_name = "TianZe F";
	char *students_333_name = "WuMing R";
	char *pBuf;

	pBuf = students_111_name;
	GET_SUTDENTS_NAME_INFO_HOOK(pBuf, STUDENTS_111);

	pBuf = students_222_name;
	GET_SUTDENTS_NAME_INFO_HOOK(pBuf, STUDENTS_222);

	pBuf = students_333_name;
	GET_SUTDENTS_NAME_INFO_HOOK(pBuf, STUDENTS_333);
}

提供了两个函数,一是注册函数egRegGetStudentsInfoFunc,功能是将干实事的函数注册进来,这时候只是留一个壳子。getStudentsInfoEntry函数就是后面要调用的函数,要将该函数中的三个学生名字给送出去,此时GET_SUTDENTS_NAME_INFO_HOOK(pBuf, STUDENTS_111);该函数具体实现的什么功能,这时候根本就不知道,要看后面注册进来的函数是实现什么功能才能确定,这时候只知道pBuf指向了三个学生名字的那块内存,至于名字怎么送?后面会讲

介绍完了上面的功能,接着讲怎么将students_info.c打包成.a文件,命令如下

gcc -c students_info.c
ar cr libstudentsInfo.a students_info.o

生成了libstudentsInfo.a文件,将生成的.a文件放到上述的library/lib文件夹下

-rwxr-xr-x 1 root root 2154 Jan 23 20:00 libstudentsInfo.a
-rwxrwxrwx 1 root root 1020 Jan 23 19:57 students_info.c
-rw-r--r-- 1 root root 1936 Jan 23 19:59 students_info.o

三:注册真正功能函数

上述壳子留好了,接下来就是要写真正的钩子了。首先来看一下源代码hookfunc_main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hookfunc_main.h"

extern int egRegGetStudentsInfoFunc(GET_SUTDENTS_NAME_INFO_Fucptr pGetStudentsInfoFuc);
extern void getStudentsInfoEntry();

T_STUDENTS_INFO g_StudentsInfo;

void getStudentsName(char *pbuf,STUDENTS_INFO_NUM sutdentsNum)
{
	if(sutdentsNum == STUDENTS_111)
	{
		memcpy(g_StudentsInfo.sutdents_111_name,pbuf,strlen(pbuf));
	}

	if(sutdentsNum == STUDENTS_222)
	{
		memcpy(g_StudentsInfo.sutdents_222_name,pbuf,strlen(pbuf));
	}

	if(sutdentsNum == STUDENTS_333)
	{
		memcpy(g_StudentsInfo.sutdents_333_name,pbuf,strlen(pbuf));
	}

}

int callback_function_register(void)
{
	int retVaule = egRegGetStudentsInfoFunc(getStudentsName);
	return retVaule;
}

int main()
{
	memset(&g_StudentsInfo,'\0',sizeof(T_STUDENTS_INFO));
	callback_function_register();
	getStudentsInfoEntry();
	printf("sutdents_111_name = %s\n",g_StudentsInfo.sutdents_111_name);
	printf("sutdents_222_name = %s\n",g_StudentsInfo.sutdents_222_name);
	printf("sutdents_333_name = %s\n",g_StudentsInfo.sutdents_333_name);
	return 0;
}

头文件hookfunc_main.h

#ifndef _HOOKFUNC_MAIN_H_
#define _HOOKFUNC_MAIN_H_

#define STUDENTS_NAME_MAX_LEN 128

typedef enum{
    STUDENTS_111 = 1,
    STUDENTS_222,
    STUDENTS_333
}STUDENTS_INFO_NUM;

typedef void (*GET_SUTDENTS_NAME_INFO_Fucptr)(char *pBuf,STUDENTS_INFO_NUM sutdentsNum);

typedef struct 
{
    char sutdents_111_name[STUDENTS_NAME_MAX_LEN];
    char sutdents_222_name[STUDENTS_NAME_MAX_LEN];
    char sutdents_333_name[STUDENTS_NAME_MAX_LEN];
} T_STUDENTS_INFO;


#endif 

getStudentsName函数就是上面提到的,具体要实现什么功能的函数。memcpy(g_StudentsInfo.sutdents_111_name,pbuf,strlen(pbuf));可以看到要实现的功能就是将pbug指向内存的数据拷贝到结构体里去。而students_info.c中的函数getStudentsInfoEntry里面pBuf正好指向了学生名字的内存。这样就通过钩子将学生的名字给勾出来了

四:makefile编写

上述提到了该模块虽然简单但是一个完整的功能模块,makefile肯定少不了,下面就贴出makefile

_BIN_NAME          = hookfuncMain
_BIN_SOURCE_PATH   = ./source/
_INCLUDE_PATH      = ./include/ \
                     $(addprefix -I,../library/include/)

_TARGET_PATH       = ./target/
_LIB_PATH          = ./library
_BIN_OBJECT_PATH   = $(_TARGET_PATH)obj/

CC                 = gcc -march=native
AR                 = ar
ECHO               = @echo
MKDIR              = mkdir -p
RM                 = rm -rf
CFLAGS             = -fPIC -Wall -O2

_LIB_DEF          += $(_LIB_PATH)/lib/libstudentsInfo.a

_BIN_SRC_C   = $(wildcard $(_BIN_SOURCE_PATH)*.c)
_BIN_OBJ     = $(addprefix $(_BIN_OBJECT_PATH),$(subst .c,.o,$(notdir $(_BIN_SRC_C))))


all:$(_TARGET_PATH)$(_BIN_NAME)
$(_TARGET_PATH)$(_BIN_NAME):$(_BIN_OBJ)
	@$(RM) -f $@
	$(CC) $(CFLAGS) -o $@ $^ $(_MACRO_DEF) $(_LIB_DEF)
	#@objdump -x $(_TARGET_PATH)$(_BIN_NAME) > $(_TARGET_PATH)symbol.txt
	@echo GO  "------------------>" $(_TARGET_PATH)$(_BIN_NAME)
	$(RM) $(_BIN_OBJECT_PATH)
	@echo GO  "------------------  !BEAUTIFUL!  ------------------"

$(_BIN_OBJECT_PATH)%.o:$(_BIN_SOURCE_PATH)%.c
	$(ECHO) CC $<
	@if [ ! -d $(_BIN_OBJECT_PATH) ]; then $(MKDIR) $(_BIN_OBJECT_PATH); fi
	$(CC) $(CFLAGS) -c $< -o $@ -I $(_INCLUDE_PATH)

.PHONY : clean

clean:
	$(RM) $(_BIN_OBJECT_PATH)

五:编译运行结果

执行编译命令生成可执行文件

[root@promote HOOKMAIN]# make clean
rm -rf ./target/obj/
[root@promote HOOKMAIN]# make
CC source/hookfunc_main.c
gcc -march=native -fPIC -Wall -O2 -c source/hookfunc_main.c -o target/obj/hookfunc_main.o -I ./include/ -I../library/include/
gcc -march=native -fPIC -Wall -O2 -o target/hookfuncMain target/obj/hookfunc_main.o  ./library/lib/libstudentsInfo.a
#@objdump -x ./target/hookfuncMain > ./target/symbol.txt
GO ------------------> ./target/hookfuncMain
rm -rf ./target/obj/
GO ------------------  !BEAUTIFUL!  ------------------

运行可执行文件

[root@promote HOOKMAIN]# cd target/
[root@promote target]# ll
total 12
-rwxr-xr-x 1 root root 8784 Jan 23 20:53 hookfuncMain
[root@promote target]# 
[root@promote target]# ./hookfuncMain 
sutdents_111_name = KaiYing Z
sutdents_222_name = TianZe F
sutdents_333_name = WuMing R

!!!!大功告成!!!!!

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ftzchina

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值