摘要:事件驱动和信号槽是构建现代 GUI 应用和高并发服务的核心。本文将详解如何在纯 C 语言中,不依赖任何第三方库,从零实现一套高效、线程安全的事件循环(Event Loop)和信号槽(Signal-Slot)系统。

项目开源地址https://gitee.com/xin___yue/XinYueC/tree/develop/

引言

在 C++ 的 Qt 框架中,QEventLoop 和信号槽机制是其灵魂所在。它们使得开发者能够以声明式的方式处理异步事件和对象间通信。那么,在没有类、没有模板、没有 RAII 的 C 语言中,我们能否复刻这一壮举?

答案是肯定的!通过 XThreadXThreadDataXSignalSlotXEvent 和 XEventLoop 等核心模块,我们可以构建一套完整的、仿 Qt 风格的异步编程基础设施。本文将带你一探究竟。

一、基石:线程与线程数据 (XThread & XThreadData)

一切异步操作都离不开线程。通过 XThread 和 XThreadData 可以将线程与其专属数据紧密绑定。

1. XThreadData: 线程的“大脑”

XThreadData 是每个线程的私有数据容器,它持有该线程的事件队列、信号槽连接表等关键信息。

// XThreadData.h
typedef struct XThreadData
{
    XVector* m_postedEvents; // 已投递的事件队列
    XMutex m_postedEventsLock;
    
    
    XObject* m_eventSender; // 当前正在处理事件的发送者
    void* m_currentEvent;   // 当前正在处理的事件
} XThreadData;
  • 关键作用: 它确保了每个线程都有独立的事件处理上下文,是实现线程安全的基础。
  • 相关源码文件:

2. XThread: 线程的“躯壳”

XThread 封装了操作系统线程,并持有一个指向其 XThreadData 的指针。

// XThread.h
typedef struct XThread
{
    XObject base; // 继承自基类
    ThreadHandle m_thread; // 平台相关的线程句柄
    XThreadData* m_data;   // 指向线程数据
    // ... 其他成员
} XThread;
  • 关键方法xthread_start() 启动线程并初始化 m_dataxthread_current_data() 获取当前线程的 XThreadData
  • 相关源码文件:

二、核心枢纽:XObject 对象模型

XObject 是整个框架中所有可交互对象的基类,它集成了事件处理、信号槽、线程亲和性、父子关系等核心功能。

1. XObject 结构体详解

XObject 的结构体设计精巧,包含了对象运行所需的所有元数据:

// XObject.h
typedef struct XObject
{
    XClass m_class;                      // 继承的基类,包含虚函数表指针
    XAtomic_uint32_t m_posted_events;    // 原子计数:已投递但未处理的事件数量

    // 对象标志位
    uint32_t is_widget : 1;         // 是否为窗口部件
    uint32_t block_sig : 1;         // 信号是否被阻塞
    uint32_t was_deleted : 1;       // 对象是否已被标记为删除
    // ... 其他标志位

    XTimerId pollId;                // 轮询定时器ID
    XSignalSlot* m_signalSlot;      // 信号与槽控制器
    XObject* parent;                // 父对象
    XVector* children;              // 子对象列表
    XObject* sender;                // 发送者对象(用于信号槽)
    XVector* filters;               // 事件过滤器列表
    XThread* m_thread;              // 所属线程指针
    XString* object_name;           // 对象名称
} XObject;

2. 核心能力:事件处理 (event)

XObject 通过虚函数 event 来处理各种类型的事件。其基础实现 VXObject_event 是一个中央分发

器:

// XObject.c
static bool VXObject_event(XObject* self, XEvent* e)
{
    switch (e->type) {
        case XEVENT_TYPE_TIMER: 
            XObject_timerEvent_base(self, e); 
            break;
        case XEVENT_TYPE_CHILD_ADDED:
        case XEVENT_TYPE_CHILD_REMOVED: 
            XChildEvent_handler(e, self); 
            break;
        case XEVENT_TYPE_META_CALL: 
            XEventMetaCall_handler(e, self); 
            break;
        case XEVENT_TYPE_DEFERRED_DELETE: 
            XEventDeferredDelete_handler(e, self); 
            break;
        // ... 其他事件类型
    }
    return e->accepted;
}
  • XEVENT_TYPE_META_CALL: 这是信号槽异步调用的关键。当一个跨线程的信号被发射时,会生成一个 MetaCallEvent 并投递到接收者线程的事件队列。event 函数收到此事件后,会调用 XEventMetaCall_handler 来执行真正的槽函数。
  • XEVENT_TYPE_DEFERRED_DELETE: 支持安全的对象延迟删除。

3. 线程亲和性:moveToThread

moveToThread 是实现跨线程通信的关键。它改变了对象的“家”——即其所属的线程。

// XObject.c
bool XObject_moveToThread(XObject* object, XThread* target_thread)
{
    // ... 健壮性检查(不能在非所属线程调用等)

    // 递归移动所有子对象
    if (object->children) {
        for_each_iterator(object->children, XVector, it) {
            XObject* child = *((XObject**)XVector_iterator_data(&it));
            XObject_moveToThread(child, target_thread);
        }
    }

    // 处理完当前线程的待处理事件,确保状态一致
    XCoreApplication_processEvents(XEventLoop_AllEvents);

    // 执行移动:更新线程指针
    object->m_thread = target_thread;

    return true;
}
  • 工作流程: 当对象 A 被移动到线程 T 后,所有发送给 A 的事件(包括信号槽调用产生的 MetaCallEvent)都会被投递到线程 T 的事件队列中,并由 T 的事件循环处理。这保证了对象在其“家”线程中被安全地访问和修改。

三、核心:信号与槽 (XSignalSlot)

信号槽是对象间通信的桥梁。XSignalSlot 模块实现了这一机制。

1. 连接管理

XSignalSlot 内部维护一个连接列表,每个连接记录了信号发送者、信号索引、槽接收者、槽索引以及连接类型(直接连接或队列连接)。

2. 发射信号

当调用 xobject_emit_signal(sender, signal_index, args) 时,XSignalSlot 会:

  1. 在连接列表中查找所有匹配的 sender 和 signal_index 的连接。
  2. 对于每个连接:
    • 如果是直接连接 (DirectConnection),且发送者和接收者在同一线程,则立即调用接收者的槽函数。
    • 如果是队列连接 (QueuedConnection),或者跨线程,则将一个MetaCallEvent 事件投递到接收者所在线程的事件队列中。

这种设计巧妙地将同步和异步调用统一起来,并天然支持跨线程通信。

四、心脏:事件系统与事件循环

事件循环是整个异步系统的驱动力。XEventXEventLoop 和 XAbstractEventDispatcher 共同构成了这一核心。

1. XEvent: 事件的抽象

XEvent 是所有事件的基类。通过 XEventType.h 中定义的枚举,我们可以区分不同类型的事件,如 XET_MetaCall(用于信号槽调用)、XET_TimerXET_Custom 等。

// XEvent.h
typedef struct XEvent
{
    XClass base;
    uint32_t m_type; // 事件类型
    uint32_t m_isAccepted : 1;
} XEvent;

2. XAbstractEventDispatcher: 事件分发器的抽象

XAbstractEventDispatcher 定义了事件循环与底层操作系统事件机制(如 selectepollkqueue)交互的接口。它负责注册定时器、I/O 通知,并在事件发生时唤醒事件循环。

3. XEventLoop: 事件循环的具体实现

XEventLoop 是事件循环的具体管理者。它的 exec() 方法是事件处理的核心:

// XEventLoop.c
int xeventloop_exec(XEventLoop* loop)
{
    while (!loop->m_quit) {
        // 1. 处理已投递的事件 (Posted Events)
        processPostedEvents();
        
        // 2. 通过 EventDispatcher 等待并处理新的 I/O 或定时器事件
        XAbstractEventDispatcher_processEvents(loop->m_dispatcher);
    }
    return loop->m_exitCode;
}
  • processPostedEvents(): 从 XThreadData 的 m_postedEvents 队列中取出事件,并分发给对应的 XObject 的 event() 方法。MetaCallEvent 就在这里被处理,从而触发槽函数的执行。
  • XAbstractEventDispatcher_processEvents(): 调用具体的事件分发器实现,阻塞等待直到有新的活动(如 socket 数据到达、定时器超时),然后处理这些活动。

通过这种方式,信号槽的异步调用(队列连接)被无缝地集成到了事件循环中。

4. XCoreApplication: 应用程序的入口

XCoreApplication 作为应用程序的主控制器,内部持有一个 XEventLoop 实例,并提供 exec() 方法来启动主事件循环。

五、工作流程示例

让我们通过一个简单的跨线程通信例子来串联整个流程:

  1. 主线程中创建一个 Worker 对象,并将其 moveToThread() 到一个新的工作线程。
  2. 主线程调用 xobject_connect(button, XSIGNAL(clicked), worker, doWork),建立一个队列连接
  3. 用户点击按钮,button 对象发射 clicked 信号。
  4. XSignalSlot 检测到这是一个跨线程的队列连接,于是创建一个 MetaCallEvent,并将其投递到 worker 对象所在线程的事件队列 (m_postedEvents) 中。
  5. 工作线程的 XEventLoop (exec()) 在下一次迭代中,从队列中取出这个 MetaCallEvent
  6. 事件循环调用 worker->event(event)worker 识别出这是一个 MetaCallEvent,于是调用其 doWork 槽函数
  7. doWork 执行完毕,工作线程继续其事件循环。

整个过程完全由框架自动管理,开发者只需关注业务逻辑(doWork 的实现),无需手动处理线程同步和消息传递。

六、总结

通过 XObjectXThreadDataXSignalSlotXEvent 和 XEventLoop 的精妙协作,在 C 语言中成功实现了现代 GUI 框架的核心——事件循环与信号槽。

这套系统不仅功能完备,支持跨线程异步通信,而且设计清晰,充分体现了 C 语言在系统编程领域的强大能力。它为那些希望在 C 语言中构建复杂、响应式应用程序的开发者提供了一个宝贵的参考和强大的工具。

探索更多细节,请访问项目源码https://gitee.com/xin___yue/XinYueC/tree/develop/

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐