前言
我在上一篇文章讲了关于用C模拟信号槽的思路,并完成了一个基本雏形,可以实现一个信号对应多个槽的功能,我原本以为这已经够用了,但是实际用起来发现根本不够用,于是我又重新构思了一下,将多个信号对应一个槽的功能也加了进来,并且还实现一个信号去连接另一个信号的功能。基本思路
由于一个槽要连接多个信号,因此定义槽的时候就不能像之前一样定义一个节点而是也要定义一个链头,然后在连接的时候去动态申请一个槽节点,这个槽节点内部包含两个节点,一个是添加到自身槽的链头,另一个添加到信号的链头,断开连接的时候,就是槽遍历自身的链表,判断哪个节点连接了该信号,然后在将节点移除,信号的思路与槽一样,因为信号也有可能去连接其他的信号,以下是代码。
signal_slot_.h
/**
* @file signal_slot.h
* @author salalei
* @brief 用C模拟的信号槽机制
* @version V0.0.1
* @date 2021-08-28
*
* @copyright Copyright (c) 2021
*
*/
#ifndef __SIGNAL_SLOT_H__
#define __SIGNAL_SLOT_H__
#ifdef __cplusplus
extern "C" {
#endif
//用户接口配置部分
//用户需要包含的头文件
#include "list.h"
#include <stdio.h>
#include <stdlib.h>
typedef struct list_node user_list_node_t; //用户提供的链表类型
#define USER_LIST_HEAD_INIT(x) LIST_HEAD_INIT(x) //用户提供链头初始化宏
#define USER_LIST_FOR_EACH_ENTRY(head, node, mem) list_for_each_entry(head, node, mem) //用户提供的遍历链表宏
#define USER_LIST_ADD(head, node) list_add_tail(head, node) //用户提供的添加节点的函数
#define USER_LIST_DEL(node) list_del(node) //用户提供的删除节点的函数
#define USER_MALLOC malloc //用户提供的申请内存的函数
#define USER_FREE free //用户提供的释放内存的函数
#define USER_WARN printf //用户提供的打印信息的函数
/**
* @brief 信号或槽的节点(内部使用)
*/
struct _signal_slot_node
{
user_list_node_t self_node; //连接自身的节点
user_list_node_t signal_node; //连接信号的节点
void *func; //执行的函数
void *signal; //归属于哪个信号
};
int _connect(struct _signal_slot_node *, struct _signal_slot_node *, struct _signal_slot_node *);
struct _signal_slot_node *_disconnect(struct _signal_slot_node *, struct _signal_slot_node *);
#define _SIGNAL_SLOT_DEF(name, ...) \
void name(struct _signal_slot_node *sender, ##__VA_ARGS__); \
struct _signal_slot_node name##_head = { \
.self_node = USER_LIST_HEAD_INIT(name##_head.self_node), \
.signal_node = USER_LIST_HEAD_INIT(name##_head.signal_node), \
.func = name, \
.signal = NULL, \
}; \
void name(struct _signal_slot_node *sender, ##__VA_ARGS__)
#define _SIGNAL_SLOT_DECL(name, ...) \
void name(struct _signal_slot_node *sender, ##__VA_ARGS__); \
extern struct _signal_slot_node name##_head
/**
* @brief 定义一个信号
*
* @param name 信号的名称
* @param ... 这个信号的参数
*/
#define SIGNAL_DEF(name, ...) \
typedef void (*name##_t)(struct _signal_slot_node * sender, ##__VA_ARGS__); \
_SIGNAL_SLOT_DEF(name, ##__VA_ARGS__)
/**
* @brief 发送信号(用在定义信号的函数里面)
*
* @param name 信号的名称
* @param ... 要发送的参数
*/
#define SIGNAL_SEND(name, ...) \
struct _signal_slot_node *node; \
name##_t func; \
USER_LIST_FOR_EACH_ENTRY(&name##_head.signal_node, node, signal_node) \
{ \
func = (name##_t)node->func; \
func(&name##_head, ##__VA_ARGS__); \
}
/**
* @brief 将已定义的信号对外申明
*
* @param name 信号的名称
* @param ... 这个信号的参数
*/
#define SIGNAL_DECL(name, ...) \
_SIGNAL_SLOT_DECL(name, ##__VA_ARGS__)
/**
* @brief 定义一个槽
*
* @param name 槽的名称
* ... 这个槽的参数
*/
#define SLOT_DEF(name, ...) \
_SIGNAL_SLOT_DEF(name, ##__VA_ARGS__)
/**
* @brief 将已定义的槽对外申明
*
* @param name 信号的名称
* @param ... 这个槽的参数
*/
#define SLOT_DECL(name, ...) \
_SIGNAL_SLOT_DECL(name, ##__VA_ARGS__)
/**
* @brief 发射一个信号
*
* @param name 需要发射的信号名称
* @param ... 需要发送的数据
*/
#define EMIT(name, ...) \
name(&name##_head, ##__VA_ARGS__)
/**
* @brief 在槽函数中判断是谁发送的信号
*
* @param name 需要判断的信号名称
* @return 如果是name发射的信号返回非0,否则返回0
*/
#define IS_SENDER(name) \
(sender == &name##_head)
/**
* @brief 将指定信号与指定槽连接(使用动态内存方式)
*
* @param signal_name 需要连接的信号名称
* @param slot_name 需要连接的槽名称
*/
#define CONNECT(signal_name, slot_name) \
do \
{ \
struct _signal_slot_node *node; \
node = USER_MALLOC(sizeof(struct _signal_slot_node)); \
if (node == NULL) \
USER_WARN("Failed to malloc %s node\n", #slot_name); \
else \
{ \
if (_connect(&signal_name##_head, &slot_name##_head, node)) \
{ \
USER_WARN("%s has been connected to the %s\n", #slot_name, #signal_name); \
USER_FREE(node); \
} \
} \
} \
while (0)
/**
* @brief 将指定信号与指定槽断开连接(使用动态内存方式)
*
* @param signal_name 需要连接的信号名称
* @param slot_name 需要连接的槽名称
*/
#define DISCONNECT(signal_name, slot_name) \
do \
{ \
struct _signal_slot_node *node; \
node = _disconnect(&signal_name##_head, &slot_name##_head); \
if (node) \
USER_FREE(node); \
else \
USER_WARN("The %s and %s have been disconnected\n", #signal_name, #slot_name); \
} \
while (0)
/**
* @brief 将指定信号与指定槽连接(使用静态内存方式)
*
* @param signal_name 需要连接的信号名称
* @param slot_name 需要连接的槽名称
*/
#define CONNECT_STATIC(signal_name, slot_name) \
do \
{ \
static struct _signal_slot_node node; \
if (_connect(&signal_name##_head, &slot_name##_head, &node)) \
USER_WARN("%s has been connected to the %s\n", #slot_name, #signal_name); \
} \
while (0)
/**
* @brief 将指定信号与指定槽断开连接(使用静态内存方式)
*
* @param signal_name 需要连接的信号名称
* @param slot_name 需要连接的槽名称
*/
#define DISCONNECT_STATIC(signal_name, slot_name) \
do \
{ \
if (_disconnect(&signal_name##_head, &slot_name##_head) == NULL) \
USER_WARN("The %s and %s have been disconnected\n", #signal_name, #slot_name); \
} \
while (0)
#ifdef __cplusplus
}
#endif
#endif
signal_slot.c
/**
* @file signal_slot.c
* @author salalei
* @brief 用C模拟的信号槽机制
* @version V0.0.1
* @date 2021-08-28
*
* @copyright Copyright (c) 2021
*
*/
#include "signal_slot.h"
/**
* @brief 连接信号槽
*
* @param signal_head 信号的链头
* @param head 被连接的信号或槽的链头
* @param node 新的信号或槽的节点
* @return int 成功返回0,若已有节点存在则返回-1
*/
int _connect(struct _signal_slot_node *signal_head, struct _signal_slot_node *head, struct _signal_slot_node *new_node)
{
struct _signal_slot_node *node;
USER_LIST_FOR_EACH_ENTRY(&head->self_node, node, self_node)
{
if (node->signal == signal_head)
return -1;
}
new_node->signal = signal_head;
new_node->func = head->func;
USER_LIST_ADD(&head->self_node, &new_node->self_node);
USER_LIST_ADD(&signal_head->signal_node, &new_node->signal_node);
return 0;
}
/**
* @brief 将指定信号和槽的连接断开
*
* @param signal_head 信号的链头
* @param slot_head 槽的链头
* @return struct _signal_slot_node* 成功返回被删除的节点,失败返回NULL
*/
struct _signal_slot_node *_disconnect(struct _signal_slot_node *signal_head, struct _signal_slot_node *slot_head)
{
struct _signal_slot_node *node;
USER_LIST_FOR_EACH_ENTRY(&slot_head->self_node, node, self_node)
{
if (node->signal == signal_head)
{
USER_LIST_DEL(&node->signal_node);
USER_LIST_DEL(&node->self_node);
return node;
}
}
return NULL;
}
list.h
/**
* @file list.h
* @author hz010153 (yunlei.zhou@3d-scantech.com)
* @brief 简单的双向链表
* @version V1.0.0
* @date 2021-08-29
*
* @copyright Copyright (c) 2021
*
*/
#ifndef __LIST_H__
#define __LIST_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#define MEMBER_OFFSET(type, mem) ((size_t) & (((type *)0)->mem))
#define CONTAINER_OF(p, type, mem) ((type *)((size_t)(p)-MEMBER_OFFSET(type, mem)))
struct list_node
{
struct list_node *prev;
struct list_node *next;
};
#define LIST_HEAD_INIT(head) \
{ \
.prev = &(head), \
.next = &(head) \
}
/**
* @brief 在链表尾部添加一个节点
*
* @param head 指向链头的指针
* @param node 指向新节点的指针
*/
static inline void list_add_tail(struct list_node *head, struct list_node *node)
{
node->next = head;
node->prev = head->prev;
head->prev->next = node;
head->prev = node;
}
/**
* @brief 删除指定的节点
*
* @param node 指向要移除的节点的指针
*/
static inline void list_del(struct list_node *node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
node->prev = node;
node->next = node;
}
/**
* @brief 正向遍历链表
*
* @param head 指向链头的指针
* @param node 返回的当前节点
* @param mem 链表在该结构体中的成员名
*/
#define list_for_each_entry(head, node, mem) \
for ((node) = CONTAINER_OF((head)->next, typeof(*node), mem); \
&(node)->mem != (head); \
(node) = CONTAINER_OF((node)->mem.next, typeof(*node), mem))
#ifdef __cplusplus
}
#endif
#endif
为了减小耦合,提高模块复用,我将链表部分的代码单独拎出来,这样的话用户可以使用我的链表,也可以使用他们自己写的,在signal_slot.h改一下配置就行,链表我参考了linux内核的链表,并只写了我要用到的几个函数。
用法就不多说了,直接上示例代码更快。
示例
signal_test.c
#include "signal_test.h"
SIGNAL_DEF(signal1, char *str)
{
SIGNAL_SEND(signal1, str);
}
SIGNAL_DEF(signal2, char *str)
{
SIGNAL_SEND(signal2, str);
}
signal_test.h
#ifndef __SIGNAL_TEST_H__
#define __SIGNAL_TEST_H__
#include "../signal_slot.h"
SIGNAL_DECL(signal1, char *);
SIGNAL_DECL(signal2, char *);
#endif
slot_test.c
#include "slot_test.h"
#include "signal_test.h"
#include <stdio.h>
static void _slot(int index, void *sender, char *content)
{
printf("[slot%d] ", index);
printf("sender:");
if (IS_SENDER(signal1))
printf("signal1");
else if (IS_SENDER(signal2))
printf("signal2");
else
printf("unknown");
printf(" content:%s\n", content);
}
SLOT_DEF(slot1, char *str)
{
_slot(1, sender, str);
}
SLOT_DEF(slot2, char *str)
{
_slot(2, sender, str);
}
slot_test.h
#ifndef __SLOT_TEST_H__
#define __SLOT_TEST_H__
#include "../signal_slot.h"
SLOT_DECL(slot1, char *);
SLOT_DECL(slot2, char *);
#endif
main.c
#include "../signal_slot.h"
#include <stdio.h>
#include "signal_test.h"
#include "slot_test.h"
int main(int argc, char **argv)
{
CONNECT(signal1, slot1);
CONNECT(signal1, slot2);
CONNECT(signal2, slot1);
CONNECT(signal2, slot2);
CONNECT(signal1, signal2);
EMIT(signal1, "hello");
EMIT(signal2, "world");
return 0;
}
我定义了两个信号,两个槽,然后每个信号连接两个槽,每个槽连接两个信号,并且又将信号1和信号2相连,以下为运行结果。
总结
感觉功能应该实现的差不多了,暂时就这样了。今天顺便上传了github,https://github.com/salalei/c_signal_slot,各位要者自取。