Qt信号槽的回调机制

问: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信号槽实现的全过程了。能跟着调试器一步步看看运行过程更能很好的理解。源码中有很多检查校验函数,善后释放都没有去实现,毕竟我们的目标是理解信号槽的机制。

完整工程示例

https://download.csdn.net/download/hanzhaoqiao1436/89431950

相关推荐
森G2 小时前
21、信号和槽详解---------QT基础
qt
西装没钱买2 小时前
QT组播的建立和使用(绑定特定的网卡,绑定特定IP)
网络·c++·qt·udp·udp组播
森G3 小时前
20、元对象系统---------QT基础
qt
Laurence3 小时前
CMake 报错 Failed to find required Qt component WebEngineWidgets
qt·webengine·cmake·找不到
习惯就好zz3 小时前
Qt Quick 系统托盘完整实践
开发语言·qt·qml·系统托盘·system tray·qapplication·qguiapplication
笨笨马甲3 小时前
Qt集成OpenCV
开发语言·qt
笨笨马甲3 小时前
Qt 工业机器视觉开发
开发语言·qt
小灰灰搞电子4 小时前
Qt 打印输出:printf与qDebug的区别
开发语言·qt
火山上的企鹅4 小时前
Qt/QGroundControl 实战:接入 Skydroid(云卓) G20 遥控器 Android SDK 并实时显示摇杆与信号质量
android·开发语言·qt·qgroundcontrol·云卓sdk
白杆杆红伞伞4 小时前
Qt进程间通信
开发语言·qt