Qt信号槽的回调机制

问: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 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_;
};
#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类

#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

相关推荐

  1. Qt信号机制

    2024-06-17 20:34:02       20 阅读
  2. 一文读懂Qt信号机制

    2024-06-17 20:34:02       32 阅读
  3. 纯Python实现Qt信号机制

    2024-06-17 20:34:02       14 阅读
  4. 深入理解Qt信号机制

    2024-06-17 20:34:02       9 阅读

最近更新

  1. 数据守卫者:sklearn中的异常点检测技术

    2024-06-17 20:34:02       0 阅读
  2. 概率解码:SKlearn中模型的概率预测指南

    2024-06-17 20:34:02       0 阅读
  3. 遇到的问题汇总

    2024-06-17 20:34:02       0 阅读
  4. Oracle中CREATE FORCE VIEW的说明和例子

    2024-06-17 20:34:02       0 阅读
  5. 探索邻近奥秘:SKlearn中K-近邻(KNN)算法的应用

    2024-06-17 20:34:02       0 阅读
  6. 简谈设计模式之工厂模式

    2024-06-17 20:34:02       0 阅读
  7. tensorflow学习笔记(二)

    2024-06-17 20:34:02       1 阅读
  8. Typescript【网址取ID传入后端API】

    2024-06-17 20:34:02       1 阅读
  9. mongodb-数据备份和恢复

    2024-06-17 20:34:02       1 阅读

热门阅读

  1. c++ 用对象还是指针;引用传递为啥可以减少拷贝

    2024-06-17 20:34:02       10 阅读
  2. 2024年反电诈重点:打击帮信罪&掩隐罪

    2024-06-17 20:34:02       8 阅读
  3. 第九章 Three.js 高级材质与着色器 (二)

    2024-06-17 20:34:02       6 阅读
  4. Jackson指定json的key

    2024-06-17 20:34:02       7 阅读
  5. 面向对象编程中的类详解

    2024-06-17 20:34:02       7 阅读
  6. 【镜像制作】docker命令的参数解释及用法

    2024-06-17 20:34:02       9 阅读
  7. NSNumber转float或double类型避免小数点后补0

    2024-06-17 20:34:02       9 阅读
  8. 使用 Selenium 保持登录会话信息

    2024-06-17 20:34:02       9 阅读
  9. MySQL触发器基本结构

    2024-06-17 20:34:02       8 阅读
  10. jingxiang制作

    2024-06-17 20:34:02       8 阅读
  11. 使用Spring Boot设计对象存储系统

    2024-06-17 20:34:02       9 阅读
  12. 在php中的序列化与反序列化

    2024-06-17 20:34:02       11 阅读