C language - dynamic library of "function registration and callback model": realizing the interaction between modules (low coupling)

Background: write a dynamic library of modules and provide necessary header files and demo s.

Thinking: first, make it clear that the library to be written provides several interfaces to the outside world. Read the structure of the relevant header file and the functions to be used in the library written by yourself.

In the actual project development, there are many library files, especially to prevent the disclosure of source code between different enterprises, which are provided to each other in the form of library, and then provide the necessary header files. In order to further strengthen the confidentiality of the source code, this situation is also common among different project groups of the same company. If each project group and each developer are independent, low-level developers will not have access to the company's core source code and master the company's business overview.

This paper introduces the function registration and callback mechanism, compiling and making dynamic link library with Makefile, and calling dynamic library with makefile.

The purpose of "callback registration" mechanism can achieve the goal of low coupling of modular programming, which is conducive to the development and maintenance of the project.

When looking up the data, I found a good article explaining "registration function and callback model": What is a callback function- Zhihu (zhihu.com)

What is a callback function?

Let's go a long way to answer this question.

Programming is divided into two categories: system programming and application programming. The so-called system programming, in short, is to write a library; Application programming is to use various libraries to write programs with certain functions, that is, applications. System programmers will leave some interfaces for their own libraries, namely API (application programming interface), Application programming interface )For application programmers. Therefore, in the diagram of the abstraction layer, the library is located below the application.

When the program runs, generally, application program The application program will often call the functions prepared in advance in the library through the API. However, some library function s require the application to pass a function to it first, so that it can be called at an appropriate time to complete the target task. This function that is passed in and then called is called a callback function( callback function).

For example, a hotel provides wake-up service, but passengers are required to decide the method of wake-up. You can call the guest room or send a waiter to knock on the door. If you sleep so hard that you are afraid of delaying things, you can also ask to pour a basin of water on your head. Here, the "wake-up" behavior is provided by the hotel, which is equivalent to the library function, but the way of wake-up is determined by the passenger and told the hotel, that is, the callback function (note that the library function and the callback function are not the same thing, and they are marked). The action of passengers telling the hotel how to wake themselves up, that is, the action of transferring the callback function to the storage function, is called to register a callback function. As shown in the figure below (picture source: Wikipedia):

(it can also be seen from the above figure that callback function and main function are on the same layer, that is, callback function and main function are marked in the same. c file)

As you can see, the callback function is usually at the same abstraction level as the application (because what callback function is passed in is determined at the application level). The callback becomes a high-level call to the bottom, and the bottom goes back to call the high-level process. (I think) this should be the earliest application of callback and the reason why it is so named.

Advantages of callback mechanism

As can be seen from the above example, the callback mechanism provides great flexibility. Please note that from now on, we will change the library function in the figure to intermediate function, because callback is not only used between application and library. Any time you want flexibility similar to the above, you can take advantage of callbacks.

How is this flexibility achieved? At first glance, the callback seems to be just a call between functions, but after careful consideration, we can find a key difference between the two: in the callback, we use some way to pass the callback function into the intermediate function like a parameter. It can be understood that the intermediate function is incomplete before passing in a callback function. In other words, the program can determine and change the behavior of the intermediate function by registering different callback functions at run time. This is much more flexible than simple function calls.

All right, let's start writing:

Record the compilation and call of Linux-C dynamic library. Encapsulate the code that implements some functions into a library file to facilitate calling, or protect and encrypt the code. Application scenario: sometimes you want to provide some code to others, but you don't want to disclose the source code. At this time, you can package the code into a library file. In the development of other personnel to call the library.

Dynamic library features:
1. The code of the library will not be compiled into the program, so the program compiled by the dynamic library is relatively small.
2. The program compiled by the dynamic library depends on the environment variable of the system. If there is no library file, it cannot run.

Static library features:
1. The code of the library will be compiled into the program, so the program compiled by the static library is relatively large
2. The program compiled by the static library does not depend on the environment variable of the system, so the environment variable can run with or without this library file.

1. Directory structure

 2. Generate dynamic library libnotice_wake.so (src directory)

        2.1 libnotice_wake.h file

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

/*Enumeration of wake-up service modes*/
enum ask_wakeup_way
{
	call_me=1,		/*phone*/
	knocking_door_me,	/*knock at the door*/	
	watering_head_me,	/*Splash water*/	
};

typedef void (*ask_for_wakeup)(int num);	/*Library (intermediate) function*/

typedef struct call_function_t		/*Library (intermediate) function structure*/
{
	int way;
	ask_for_wakeup function;
}call_function;

int registe_callback(call_function *reg_fun);		/*Registry (intermediate) function*/
void notice_event(int num);	/*Event function*/

#endif

         2.2 libnotice_wake.c) documents

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

/*Enumeration of wake-up service modes*/
enum ask_wakeup_way
{
	call_me=1,		/*phone*/
	knocking_door_me,	/*knock at the door*/	
	watering_head_me,	/*Splash water*/	
};

typedef void (*ask_for_wakeup)(int num);	/*Library (intermediate) function*/

typedef struct call_function_t		/*Library (intermediate) function structure*/
{
	int way;
	ask_for_wakeup function;
}call_function;

int registe_callback(call_function *reg_fun);		/*Registry (intermediate) function*/
void notice_event(int num);	/*Event function*/

#endif
[root@localhost src]# cat libnotice_wake.c
/*Library module
 *Implement registration function
 *Implement event triggered callback
 */
#include "libnotice_wake.h"

/*Define a function pointer global variable*/
ask_for_wakeup g_wakeup_ptr;

int registe_callback(call_function *reg_fun)
{
	
	/*Registration: g_wakeup_ptr is a function pointer*/
	g_wakeup_ptr = reg_fun->function;

	return 0;
}

/*Event function*/
void notice_event(int event)
{
	g_wakeup_ptr(event);	
}

2.3 generate Makefile file of dynamic library

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. Call dynamic library to generate notice_wake_app.1.0.0.1 executable (bin directory)

Applications load dynamic libraries in two ways:

  • Load the dynamic library when the application executes
  • Load the dynamic library when compiling the application (the compilation method used in this article)

The dynamic library files compiled in this way also need to be in / etc / LD so. Add libmytest.exe to the conf.d/ directory The path description of the so library file, that is, in / etc / LD so. Create a new configuration file mytest. In the conf.d/ directory Conf and execute the ldconfig command, / etc / LD so. conf.d/mytest. The file content of conf is libmytest Absolute path of so library file, for example: / root/workspace/callback_example_2/lib (this is the content of the mytest.conf configuration file)

3.1 main program

#include "libnotice_wake.h"

/*This is the callback function*/
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;

	/*Registering Callbacks */
	ret = registe_callback(&ptr_get_up);
	if (-1 == ret)
	{
		printf("Register failed.\n");
	}	
	
	while(1)
	{
		printf("Please input wakeup way:");
		scanf("%d", &event);
		
		/*Exit loop*/
		if (0 == event)
		{
			return ;
		}
		
		/*Trigger event*/
		notice_event(event);
	}
	
	return 0;	
}

3.2 generate executable file} notice_ wake_ app. Makefile file for 1.0.0.1

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 

The following situation will be modified later:

Callback function void callback_fun(int event), if it can be split into many xxx(); xxx(); xxx();, The demonstration effect will be much better. switch is used to handle different events, and the final callback function is always callback_fun, which does not reflect the characteristics of registration and callback. In fact, to put it bluntly, there is a processing process, and there may be many processing situations. We write the processing of each situation as a function separately (of course, the parameters and return values of these functions are the same), and then write the complete processing process as a function pointer, and put the corresponding processing function address into that pointer according to different events, The process of selecting a function address to put into a pointer is called registration. Complete the switching of different functions under the same interface with variable function pointers.

Keywords: C Back-end

Added by curtis_b on Thu, 20 Jan 2022 01:21:12 +0200