C++进阶专栏:http://t.csdnimg.cn/wt01A
目录
1.现象
首先定义了一个消息通知接口:
LinkMessageNotify.h
#pragma once
class ILinkMessageNotify
{
public:
virtual void notify(int value) = 0;
};
接着定了设备接口和设备类:
IDevice.h
#ifndef _I_DEVICE_H_
#define _I_DEVICE_H_
#include <assert.h>
class IDevice
{
public:
virtual ~IDevice() {}
public:
virtual int deviceID() const = 0;
virtual int type() const = 0;
virtual int open() = 0;
virtual int close() = 0;
virtual int reset() = 0;
virtual int state() const = 0;
virtual int control(int type, const void* pParam, int paramLen) = 0;
virtual int write(const char*, int) = 0;
virtual int write(const char*, int, void*) = 0;
virtual int read(char*, int) = 0;
virtual int read(char*, int, void*) = 0;
virtual int setProperty(const char* name, const void* vlaue) = 0;
};
class CDeviceAdapter : public IDevice
{
public:
virtual ~CDeviceAdapter() {}
public:
int deviceID() const override { assert(false); return -1; }
int type() const override { assert(false); return -1; }
int open() override { assert(false); return -1; }
int close() override { assert(false); return -1; }
int reset() override { assert(false); return -1; }
int state() const override { assert(false); return -1; }
int control(int type, const void* pParam, int paramLen) override { assert(false); return -1; }
int write(const char*, int) override { assert(false); return -1; }
int read(char*, int) override { assert(false); return -1; }
int write(const char*, int, void*) override { assert(false); return -1; }
int read(char*, int, void*) override { assert(false); return -1; }
int setProperty(const char* name, const void* vlaue) { assert(false); return -1; }
};
#endif
这个是我们在项目上用的一个通用设备接口类,涵盖了对设备的控制和数据交互。
LinkDevice.h
#pragma once
#include "IDevice.h"
#include "LinkMessageNotify.h"
class CLinkDevice : public CDeviceAdapter
{
public:
explicit CLinkDevice();
public:
int write(const char* data, int len) override;
int setProperty(const char* name, const void* value) override;
private:
ILinkMessageNotify* m_pMessageNotify;
};
LinkDevice.cpp
#include "LinkDevice.h"
CLinkDevice::CLinkDevice()
: m_pMessageNotify(nullptr)
{
}
int CLinkDevice::write(const char* data, int len)
{
if (m_pMessageNotify) {
m_pMessageNotify->notify(2142);
}
return 1;
}
int CLinkDevice::setProperty(const char* name, const void* value)
{
m_pMessageNotify = (ILinkMessageNotify*)static_cast<const ILinkMessageNotify*>(value);
return 1;
}
CLinkDevice是具体的设备对象,目前只实现了write和setProperty接口,因为在这里只是测试就没有实现更多的接口。
LinkMainWindow.h
#pragma once
#include <QWidget>
#include "LinkMessageNotify.h"
#include "IDevice.h"
class CLinkMainWindow : public QWidget
, public ILinkMessageNotify
{
Q_OBJECT
public:
explicit CLinkMainWindow(IDevice* pDevice, QWidget* parent = nullptr);
public:
void notify(int value) override;
private:
int m_value;
};
LinkMainWindow.cpp
#include "LinkMainWindow.h"
CLinkMainWindow::CLinkMainWindow(IDevice* pDevice, QWidget* parent)
: QWidget(parent)
{
pDevice->setProperty("NotifyInterface", this);
}
void CLinkMainWindow::notify(int value)
{
m_value = value;
//界面显示等等,此处省略。。。
//...
}
CLinkMainWindow是设备关联的窗口类,从上述代码可以看到在CLinkMainWindow的构造函数中把自己注入到CLinkDevice对象中,CLinkDevice一旦有新的消息就会立即通知窗口显示。
测试用例
#include "MyTest.h"
#include "LinkMainWindow.h"
#include "LinkDevice.h"
MyTest::MyTest(QWidget* parent)
: QMainWindow(parent)
{
ui.setupUi(this);
init();
}
MyTest::~MyTest()
{}
void MyTest::init()
{
QObject::connect(ui.pushButton, &QPushButton::clicked, this, [&]() {
//[1]
CLinkDevice device;
//[2]
CLinkMainWindow mainWindow(&device);
//[3]
device.write(nullptr, 0);
});
}
上述代码模拟了整个流程;正常情况下,程序最后会走到CLinkMainWindow::notify函数中,但是事实真的会如此吗?
我们断点调试,结构走到了如下图的代码位置:
看到这个现象刚开始一脸懵逼,怎么会走到这里呢?本来该走到CLinkMainWindow::notify的,但是却跑到了QMetaObject *CLinkMainWindow::metaObject()。
2.分析流程
1) 仔细思考一下,虚函数调错了,首先怀疑notify函数是不是跟系统函数冲突了,但是我查询了QWidget、QObject都没有notify函数,所以肯定不存在冲突。
2)接着怀疑对象CLinkDevice中的m_pMessageNotify指针指向错地方了,于是断点调试查看指针的指向:
发现m_pMessageNotify的虚表_vfptr执行的虚函数地址不对,通过比对,_vfptr是QObject的虚表指针,意思是m_pMessageNotify的_vfptr指向的是QObject的虚函数表地址,QObject的虚表为:
由于ILinkMessageNotify只有一个虚接口,因此调用notify就调到了QMetaObject *CLinkMainWindow::metaObject() 处。
3)通过上面的分析,那么是哪一个步骤错了呢?层层分析,锁定在了CLinkDevice::setProperty的位置,语句m_pMessageNotify = (ILinkMessageNotify*)static_cast<const ILinkMessageNotify*>(value); 强制转换指针出错了。CLinkMainWindow内存对象模型如下:
通过static_cast强制转换就把QWidge*转换成了ILinkMessageNotify*,QWidge的父类是QObject,调用notify函数就相当于调用了QObject的虚函数metaObject()。
3.解决方案
通过前面的分析,找到了问题的根本原因是用static_cast强制转换指针导致的。
所以在CLinkMainWindow的构造函数调用setProperty之前就要把this指针转换成ILinkMessageNotify*,于是修改为:
ILinkMessageNotify* pNotify = this;
pDevice->setProperty("NotifyInterface", pNotify);
再次调试,结果对了。
4.总结
本文涉及到的知识点有:
1) C++对象的内存模型(内存布局)
2)数据转换static_cast的用法,延伸的还有const_cast、dynamic_cast、reinterpret_cast。
5.源码下载地址
运行环境:VS2019、Qt5.12.12
https://download.csdn.net/download/haokan123456789/89120988?spm=1001.2014.3001.5501