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

相关推荐
用户805533698033 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner3 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz8 天前
QML Hello World 入门示例
qt
xcyxiner11 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner12 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner12 天前
DicomViewer (添加模型类)3
qt
xcyxiner13 天前
DicomViewer (目录调整) 2
qt
xcyxiner13 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能15 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G15 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt