前言
我在上一篇文章讲了关于用C模拟信号槽的思路,并完成了一个基本雏形,可以实现一个信号对应多个槽的功能,我原本以为这已经够用了,但是实际用起来发现根本不够用,于是我又重新构思了一下,将多个信号对应一个槽的功能也加了进来,并且还实现一个信号去连接另一个信号的功能。
基本思路
由于一个槽要连接多个信号,因此定义槽的时候就不能像之前一样定义一个节点而是也要定义一个链头,然后在连接的时候去动态申请一个槽节点,这个槽节点内部包含两个节点,一个是添加到自身槽的链头,另一个添加到信号的链头,断开连接的时候,就是槽遍历自身的链表,判断哪个节点连接了该信号,然后在将节点移除,信号的思路与槽一样,因为信号也有可能去连接其他的信号,以下是代码。
signal_slot_.h
#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
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
#define SIGNAL_DEF(name, ...) \
typedef void (*name##_t)(struct _signal_slot_node * sender, ##__VA_ARGS__); \
_SIGNAL_SLOT_DEF(name, ##__VA_ARGS__)
#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__); \
}
#define SIGNAL_DECL(name, ...) \
_SIGNAL_SLOT_DECL(name, ##__VA_ARGS__)
#define SLOT_DEF(name, ...) \
_SIGNAL_SLOT_DEF(name, ##__VA_ARGS__)
#define SLOT_DECL(name, ...) \
_SIGNAL_SLOT_DECL(name, ##__VA_ARGS__)
#define EMIT(name, ...) \
name(&name##_head, ##__VA_ARGS__)
#define IS_SENDER(name) \
(sender == &name##_head)
#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)
#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)
#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)
#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
#include "signal_slot.h"
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;
}
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
#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) \
}
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;
}
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;
}
#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,各位要者自取。
|