问:Qt强大的地方在哪里?
答:跨平台、信号槽。。。
问:信号槽是什么?
答:回调函数
问:怎么个回调法子
答:。。。
成果
信号槽本身实现过程是有些复杂的,所以本人参考了很早很早很早版本的Qt 1.41。目的很简单,就是想看看信号槽究竟是怎么回调的。先看看咱的仿信号槽成果:
两个测试类,一个用来发信号,一个用来响应信号
//TestSignal.h
#pragma once
#include "Mobject.h"
class TestSignal : public Mobject
{M_OBJECT
public:TestSignal(){}MySignals:void signalEvent();
};//TestSignal.cpp
#include "TestSignal.h"//你没看错, TestSignal.cpp就是啥都没有
//TestSlot.h
#pragma once
#include "Mobject.h"
class TestSlot : public Mobject
{M_OBJECT
public:TestSlot() {}public MySlots :void slotEvent();
};//TestSlot.cpp
#include "TestSlot.h"
#include <stdio.h>void TestSlot::slotEvent()
{printf("slotEvent invoked\n");
}
测试信号槽关联
#include <iostream>
#include <thread>
#include <Windows.h>
#include "TestSignal.h"
#include "TestSlot.h"int main()
{//用来触发信号TestSignal* sig = new TestSignal;//用来响应信号的槽TestSlot* slot = new TestSlot;//信号和槽建立关联Mobject::connect(sig, SIGNAL(signalEvent()), slot, SLOT(slotEvent()));//测试开始std::thread t1([&sig]() {while (true) {Sleep(1000);MyEmit sig->signalEvent();}});t1.join();
}
结果:
槽被成功触发了,完结撒花~~ (才怪)
正文
实现一个我们自己的QObject, 就叫Mobject吧,只写一些信号槽机制相关的宏和成员,其他没啥关系的成员我们就不要了。
#pragma once
#include <map>#define SIGNAL(a) "2"#a
#define SLOT(a) "1"#a#define MySignals public
#define MySlots
#define MyEmitclass MetaObject;
class Connection;
#define M_OBJECT \
public: \MetaObject *metaObject() const { return metaObj; } \
protected: \void initMetaObject(); \
private: \static MetaObject *metaObj;//基类 仿QObject
class Mobject
{
public:Mobject(){}~Mobject(){}virtual MetaObject* metaObject() const { return metaObj; }virtual void initMetaObject();virtual MetaObject* queryMetaObject() const;void active_signals(const char* signal);static bool connect(const Mobject *sender, const char *signal,const Mobject *receiver, const char *member);static MetaObject* metaObj;std::map<std::string, Connection*> conns_;
};
#include "Mobject.h"
#include "MetaObject.h"MetaObject* Mobject::metaObj = nullptr;void Mobject::initMetaObject()
{metaObj = new MetaObject(nullptr, nullptr);
}MetaObject * Mobject::queryMetaObject() const
{Mobject *x = (Mobject*)this;MetaObject* m = x->metaObject();if (m == nullptr)x->initMetaObject();m = x->metaObject();if (m)return m;elsereturn nullptr;
}void Mobject::active_signals(const char * signal)
{auto it = conns_.find(signal);if (it == conns_.end())return;typedef void (Mobject::*RT)();Connection* c = it->second;Mobject* obj = c->obj_;RT r = *((RT*)(&c->mbr_));(obj->*r)();
}bool Mobject::connect(const Mobject *sender, const char *signal,const Mobject *receiver, const char *member)
{/*跳过检查数据的正确性*///MetaObject* sMeta = sender->queryMetaObject();MetaObject* rMeta = receiver->queryMetaObject();signal++; //去掉前面的 2member++; //去掉前面的 1MetaData* rm = rMeta->slot(member);Connection* c = new Connection(receiver, rm->ptr, rm->name);((Mobject*)sender)->conns_.insert({std::string(signal), c });return true;
}
宏
SIGNAL 和 SLOT 完全照抄,就是在信号函数前面加上一个“2”,槽函数前面加上一个“1”,这两个值就是为了标记区分信号和槽的。
MySignals 用来定义一个信号
MySlots 用来定义一个槽函数
MyEmit 用来定义发射信号
M_OBJECT 就是缩减版的Q_OBJECT 宏
成员函数:
active_signals 发射信号其实就是调用的这个函数,它内部会找到关联的槽函数,并调用槽函数,当然我们这里只是为了了解过程,所以仅仅只调用了一个槽函数。
connect 函数是建立信号和槽的主要实现。
再来看下MetaObject类
#pragma once
#include "Connection.h"
#include <map>struct MetaData {char* name;MemberPtr ptr;
};class MetaObject
{
public:MetaObject(MetaData *slots, MetaData *signals);MetaData* slot(const char*);MetaData* signal(const char*);std::map<std::string, MetaData*> slotds_;std::map<std::string, MetaData*> signalds_;
};
#include "MetaObject.h"MetaObject::MetaObject(MetaData *slots, MetaData *signals)
{if (signals)signalds_.insert({ std::string(signals->name), signals });if(slots)slotds_.insert({ std::string(slots->name), slots });
}MetaData * MetaObject::slot(const char * name)
{auto it = slotds_.find(name);if (it == slotds_.end())return nullptr;return it->second;
}MetaData * MetaObject::signal(const char * name)
{auto it = signalds_.find(name);if (it == signalds_.end())return nullptr;return it->second;
}
可以看到,它扮演了Object的助手职责,后续会通过moc_xxx.cpp来实现记录类中定义的信号和槽。
Connection辅助类
#pragma once
#include "Mobject.h"
typedef void (Mobject::*MemberPtr)();class Connection
{
public:Connection(const Mobject*, MemberPtr, const char* memberName);~Connection(){}Mobject *obj_;MemberPtr mbr_;const char* mbrName_;
};
#include "Connection.h"Connection::Connection(const Mobject *obj, MemberPtr mbr, const char * memberName)
{obj_ = (Mobject*)obj;mbr_ = mbr;mbrName_ = memberName;
}
obj_ 对象指针
mbr_ 成员函数
mbrName_ 成员函数标识,一般就是对应着 SIGNAL(xxx) 和 SLOT(xxx)。
有了这些基础设施。再来手动实现moc.exe的功能,手动生成TestSignal.h对应的moc_TestSignal.cpp 和 TestSlot.h对应的moc_TestSlot.cpp
#include "TestSignal.h"
#include "MetaObject.h"MetaObject* TestSignal::metaObj = nullptr;
void TestSignal::initMetaObject()
{if (metaObj)return;typedef void(TestSignal::*m2_t0)();m2_t0 s0 = &TestSignal::signalEvent;MetaData *signal_tbl = new MetaData();signal_tbl->name = _strdup("signalEvent()");signal_tbl->ptr = *(MemberPtr*)&s0;metaObj = new MetaObject(nullptr, signal_tbl);
}void TestSignal::signalEvent()
{this->active_signals("signalEvent()");
}
#include "TestSlot.h"
#include "MetaObject.h"MetaObject* TestSlot::metaObj = nullptr;
void TestSlot::initMetaObject()
{if (metaObj)return;typedef void(TestSlot::*m2_t0)();m2_t0 s0 = &TestSlot::slotEvent;MetaData *slot_tbl = new MetaData();slot_tbl->name = _strdup("slotEvent()");slot_tbl->ptr = *(MemberPtr*)&s0;metaObj = new MetaObject(slot_tbl, nullptr);
}
就是把 M_OBJECT宏里面的 initMetaObject给实现出来,把定义的信号函数自动实现下,信号和槽通过initMetaObject函数都记录到metaObj中。
以上就是精简过很多以后的仿Qt信号槽实现的全过程了。能跟着调试器一步步看看运行过程更能很好的理解。源码中有很多检查校验函数,善后释放都没有去实现,毕竟我们的目标是理解信号槽的机制。
完整工程示例
https://download.csdn.net/download/hanzhaoqiao1436/89431950