用C模拟QT的信号槽机制(下)


前言

我在上一篇文章讲了关于用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,各位要者自取。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Qt信号机制作为Qt框架的重要特性,具有很多优点,如松耦合、类型安全、跨线程通信等。然而,即便如此,它也存在一些缺点。 首先,信号机制的语法相对复杂,需要开发者熟悉和理解一定的概念和规则。虽然Qt提供了一些工具和文档来帮助开发者学习和使用信号机制,但对刚接触Qt的开发者来说,仍然需要花费一定的时间和精力去理解它。 其次,信号机制的性能相对较低。每个信号之间的连接都需要一定的开销,如果使用过度,可能导致程序变慢。尤其是在高频繁触发的场景下,如实时图形界面更新,信号机制可能成为瓶颈,需要额外的优化和处理。 另外,信号机制对于多线程的支持不够友好。Qt提供了一些机制来处理多线程下的信号通信,如Qt::QueuedConnection和Qt::BlockingQueuedConnection,但开发者需要小心地处理线程间的同步和互斥问题,以避免潜在的死锁和竞态条件。 此外,信号机制在特定情况下可能导致代码的可维护性降低。当一个信号可以连接到多个时,开发者需要追踪和管理这些连接,以及处理可能的循环依赖和内存泄漏问题。这要求开发者对信号的连接和断开有很好的理解,并编写清晰可读的代码。 综上所述,虽然Qt信号机制是一个强大而灵活的工具,但在使用时需要注意以上一些缺点,以确保代码的性能和可维护性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值