C语言-“函数注册与回调模型”的动态库:实现模块间的交互(低耦合)

背景:编写一个模块的动态库,提供了必要的头文件和demo。

思考:首先弄清楚,要编写的库对外及提供几个接口。读懂相关头文件的结构体和自己写的库要用的函数。

在实际项目开发当中,有很多的库文件,尤其是在不同企业之间防止源码的公开,都是以库的形式提供给对方,然后提供必要的头文件。为了进一步加强源码的保密性,这种情况也普遍存在于同一个公司的不同项目组之间,将各个项目组,各个开发人员独立开来,级别低的开发者就接触不到公司核心源码,掌握公司的业务全貌。

本文介绍了,函数注册与回调机制,使用Makefile编译制作动态链接库,以及使用Makefile调用动态库。

“回调-注册”机制的目的可以实现模块化编程低耦合的目标,利于项目的开发和维护。

在查资料时,找到一篇对 “注册函数与回调模型” 解释的很好的文章:回调函数(callback)是什么? - 知乎 (zhihu.com)

什么是回调函数?

我们绕点远路来回答这个问题。

编程分为两类:系统编程(system programming)和应用编程(application programming)。所谓系统编程,简单来说,就是编写;而应用编程就是利用写好的各种库来编写具某种功用的程序,也就是应用。系统程序员会给自己写的库留下一些接口,即API(application programming interface,应用编程接口),以供应用程序员使用。所以在抽象层的图示里,库位于应用的底下。

当程序跑起来时,一般情况下,应用程序(application program)会时常通过API调用库里所预先备好的函数。但是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function

打个比方,有一家旅馆提供叫醒服务,但是要求旅客自己决定叫醒的方法。可以是打客房电话,也可以是派服务员去敲门,睡得死怕耽误事的,还可以要求往自己头上浇盆水。这里,“叫醒”这个行为是旅馆提供的,相当于库函数,但是叫醒的方式是由旅客决定并告诉旅馆的,也就是回调函数注意库函数和回调函数不是一回事儿,标注。而旅客告诉旅馆怎么叫醒自己的动作,也就是把回调函数传入库函数的动作,称为登记回调函数(to register a callback function)。如下图所示(图片来源:维基百科):

(通过上图也可以看出,回调函数-callback function 和 Main函数是同一层,也就是回调函数和main 函数在一个 .c文件里,标注)

可以看到,回调函数通常和应用处于同一抽象层(因为传入什么样的回调函数是在应用级别决定的)。而回调就成了一个高层调用底层,底层再回过头来调用高层的过程。(我认为)这应该是回调最早的应用之处,也是其得名如此的原因。

回调机制的优势

从上面的例子可以看出,回调机制提供了非常大的灵活性。请注意,从现在开始,我们把图中的库函数改称为中间函数了,这是因为回调并不仅仅用在应用和库之间。任何时候,只要想获得类似于上面情况的灵活性,都可以利用回调。

这种灵活性是怎么实现的呢?乍看起来,回调似乎只是函数间的调用,但仔细一琢磨,可以发现两者之间的一个关键的不同:在回调中,我们利用某种方式,把回调函数像参数一样传入中间函数。可以这么理解,在传入一个回调函数之前,中间函数是不完整的。换句话说,程序可以在运行时,通过登记不同的回调函数,来决定、改变中间函数的行为。这就比简单的函数调用要灵活太多了。

好了现在开始编写:

          记录-Linux-C动态库的编译与调用。将实现某部分功能的代码封装成库文件,以方便调用,或者是对代码进行保护加密。应用场景:有时想将某代码提供给别人用,但是又不想公开源代码,这时可以将代码封装成库文件。在开发中调用其他人员编写的库。

      动态库特点:
        1,库的代码不会编译进程序里面,所以动态库编译的程序比较小。
        2,由动态库编译的程序依赖于系统的环境变量有没有这个库文件,没有则运行不了。

     静态库特点:
        1,库的代码会编译进程序里面,所以静态库编译的程序比较大
        2,由静态库编译的程序不用依赖于系统的环境变量,所以环境变量有没有这个库文件,也可以运行。

1. 目录结构

 2. 生成动态库 libnotice_wake.so(src目录)

        2.1 libnotice_wake.h 文件

#ifndef __LIBNOTICE_WAKE_H
#define __LIBNOTICE_WAKE_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>

/*叫醒服务方式的枚举*/
enum ask_wakeup_way
{
	call_me=1,		/*打电话*/
	knocking_door_me,	/*敲门*/	
	watering_head_me,	/*泼水*/	
};

typedef void (*ask_for_wakeup)(int num);	/*库(中间)函数*/

typedef struct call_function_t		/*库(中间)函数结构体*/
{
	int way;
	ask_for_wakeup function;
}call_function;

int registe_callback(call_function *reg_fun);		/*注册库(中间)函数*/
void notice_event(int num);	/*事件函数*/

#endif

         2.2 libnotice_wake.c 文件

/*库模块
 *实现注册函数
 *实现事件触发回调
 */
#include "libnotice_wake.h"

/*定义一个函数指针全局变量*/
ask_for_wakeup g_wakeup_ptr;

int registe_callback(call_function *reg_fun)
{
	
	/*注册:g_wakeup_ptr是个函数指针*/
	g_wakeup_ptr = reg_fun->function;

	return 0;
}

/*事件函数*/
void notice_event(int event)
{
	g_wakeup_ptr(event);	
}

         2.3 生成动态库的 Makefile 文件

CC = gcc 
CFLAGS = -Wall -g -O -fPIC
CXXFLAGS = 
INCLUDE  =  ./inc 
TARGET   = libnotice_wake.so
LIBPATH  = ./libs/
 
vpath %.h ./inc
 
OBJS = libnotice_wake.o 
SRCS = libnotice_wake.c 
 
 
 
all:$(TARGET) $(OBJS)
$(OBJS):$(SRCS) 
	$(CC) $(CFLAGS) -I $(INCLUDE) -c $^
 
$(TARGET):$(OBJS)
	$(CC)  $(OBJS) -shared -fPIC -o $(TARGET)
	mv $(TARGET) $(LIBPATH)
 
 
 
clean:
	rm -f *.o
	rm -f $(LIBPATH)*

3. 调用动态库生成 notice_wake_app.1.0.0.1可执行文件(bin目录)

应用程序以两种方式加载动态库:

  • 在应用程序执行时加载动态库
  • 在编译应用程序时加载动态库(本文使用的编译方法)

对于这种方式编译出来的动态库文件,还需要在 /etc/ld.so.conf.d/ 目录中添加 libmytest.so 库文件的路径说明,即在 /etc/ld.so.conf.d/ 目录中新建配置文件 mytest.conf,且执行 ldconfig 命令, /etc/ld.so.conf.d/mytest.conf 的文件内容为 libmytest.so 库文件的绝对路径,例如:/root/workspace/callback_example_2/lib(这是mytest.conf配置文件的内容)

        3.1 main 程序

#include "libnotice_wake.h"

/*这个才是回调函数*/
void callback_function(int event)
{
	switch(event)
	{
		case call_me:
			{
				printf("Hello,this is hotel servicer.Get up!\n");
			}
			break;
		case knocking_door_me:
			{
				printf("peng~ peng~ peng~.Get up!\n");
			
			}
			break;
		case watering_head_me:
			{
				printf("hua hua hua. fuck!\n");
				
			}
			break;
		default:
			{
				printf("Unsupport wakeup way. Please input 1 to 3 !\n");
					
			}
	}	
}


int main()
{
	int ret = 0;
	int event = 0;	

	call_function ptr_get_up;
	ptr_get_up.way = 1;
	ptr_get_up.function = callback_function;

	/*注册回调函数*/
	ret = registe_callback(&ptr_get_up);
	if (-1 == ret)
	{
		printf("Register failed.\n");
	}	
	
	while(1)
	{
		printf("Please input wakeup way:");
		scanf("%d", &event);
		
		/*退出循环*/
		if (0 == event)
		{
			return ;
		}
		
		/*触发事件*/
		notice_event(event);
	}
	
	return 0;	
}

        3.2 生成可执行文件 notice_wake_app.1.0.0.1 的 Makefile 文件

CROSS = 
CC = $(CROSS)gcc
CXX = $(CROSS)g++
DEBUG = -g -O2
CFLAGS = $(DEBUG) -Wall -c
RM = rm -rf

SRCS = $(wildcard ./*.c)
OBJS = $(patsubst %.c, %.o, $(SRCS))

HEADER_PATH = -I../include/
LIB_PATH = -L../lib/

LIBS = -lnotice_wake    
# LIBS = libdiv.a

VERSION = 1.0.0.1
TARGET = notice_wake_app.$(VERSION)

$(TARGET) : $(OBJS)
	$(CC) $^ -o $@ $(LIB_PATH) $(LIBS)

$(OBJS):%.o : %.c
	$(CC) $(CFLAGS) $< -o $@ $(HEADER_PATH)

clean:
	$(RM) $(TARGET) *.o 

下面的这种情况待日后再修改:

回调函数 void callback_fun(int event),如果能拆分成许多 xxx(); xxx(); xxx();,演示效果会好很多。里面用 switch去处理不同事件,最终回调的函数始终是 callback_fun,体现不出注册、回调的特性。其实说白了就是有一个处理过程,可能会有很多种处理情况,我们把每一种情况的处理都单独写成一个函数(当然这些函数的参数、返回值都是相同的),再将这个完整的处理过程写作一个函数指针,根据事件的不同,来将对应的处理函数地址放到那个指针中,选择函数地址放入指针的这个过程叫做注册。完成用可变的函数指针,去实现相同接口下不同功能的切换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值