问:Qt强大的地方在哪里?
答:跨平台、信号槽。。。
问:信号槽是什么?
答:回调函数
问:怎么个回调法子
答:。。。
成果
信号槽本身实现过程是有些复杂的,所以本人参考了很早很早很早版本的Qt 1.41。目的很简单,就是想看看信号槽究竟是怎么回调的。先看看咱的仿信号槽成果:
两个测试类,一个用来发信号,一个用来响应信号
cpp
//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就是啥都没有
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");
}
测试信号槽关联
cpp
#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吧,只写一些信号槽机制相关的宏和成员,其他没啥关系的成员我们就不要了。
cpp
#pragma once
#include <map>
#define SIGNAL(a) "2"#a
#define SLOT(a) "1"#a
#define MySignals public
#define MySlots
#define MyEmit
class 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_;
};
cpp
#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;
else
return 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++; //去掉前面的 2
member++; //去掉前面的 1
MetaData* 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类
cpp
#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_;
};
cpp
#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辅助类
cpp
#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_;
};
cpp
#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
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()");
}
cpp
#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信号槽实现的全过程了。能跟着调试器一步步看看运行过程更能很好的理解。源码中有很多检查校验函数,善后释放都没有去实现,毕竟我们的目标是理解信号槽的机制。
完整工程示例