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

相关推荐
Mr.Q3 小时前
Qt多边形填充/不填充绘制
qt
可峰科技3 小时前
斗破QT编程入门系列之二:认识Qt:编写一个HelloWorld程序(四星斗师)
开发语言·qt
7年老菜鸡5 小时前
策略模式(C++)三分钟读懂
c++·qt·策略模式
huanggang9829 小时前
在Ubuntu22.04上使用Qt Creator开发ROS2项目
qt·ros2
老秦包你会9 小时前
Qt第三课 ----------容器类控件
开发语言·qt
spygg10 小时前
Qt低版本多网卡组播bug
qt·组播·多网卡组播·qt5.7.0
码农客栈11 小时前
qt QWebSocketServer详解
qt
plmm烟酒僧12 小时前
Windows下QT调用MinGW编译的OpenCV
开发语言·windows·qt·opencv
Black_Friend13 小时前
关于在VS中使用Qt不同版本报错的问题
开发语言·qt
CSUC13 小时前
【Qt】QTreeView 和 QStandardItemModel的关系
qt