问题如题目所示:QMetaObject::invokeMethod: No such method xxxx
,在网上好一顿查,又将查到的资料喂给了 Ai,才最终将问题解决,特此记录下。
一、问题背景
在做公司项目时,使用了插件的方式开发。主程序加载了一个叫CtmAboutAppPlugin
的插件。该插件有个界面类CtmAboutUi
,它有个槽函数:
cpp
public slot:
void subAppInfo(const QVariant& msg);
主程序通过一个叫 CbbEventBus
的事件总线机制,把消息发送给CtmAboutAppPlugin
这个插件。
此处我给出CbbEventBus
的订阅方法的源码(问题出在了订阅方法这里):
cpp
bool CbbEventBus::subscribe(const QString &topic, QObject *receiver, const char *method, Qt::ConnectionType type)
{
if (!receiver || !method) return false;
auto connection = QObject::connect(
this,
&CbbEventBus::signalUpdateMessage,
receiver,
[receiver, method, topic](const QString &receivedTopic, const QVariant &msg) {
if (receivedTopic == topic)
{
// 问题出现在下面这里
// QMetaObject::invokeMethod(receiver, method, Q_ARG(const QVariant&, msg)); // 最开始我是这么写的
QMetaObject::invokeMethod(receiver, method, Q_ARG(QVariant, msg)); // 后来改成了左边这样
}
},
type
);
if (connection) {
m_QIsSubscribeMap[topic][receiver].append(connection);
return true;
}
return false;
}
上面两种写法,在程序运行时,控制台分别输出以下信息:
Q_ARG(QVariant, msg)
对应控制台输出信息:QMetaObject::invokeMethod: No such method CtmAboutUi::1subAppInfo(QVariant)(const QVariant&)
Q_ARG(QVariant, msg)
对应控制台输出信息:No such method CtmAboutUi::1subAppInfo(QVariant)(QVariant)
二、🔍 问题原因(一句话总结)
根本原因不是方法不存在,而是 invokeMethod
找方法时用的名字"对不上号"------我传了个"带参数的签名",它却以为这是"方法名",导致匹配失败。
三、🕵️♂️ 排查过程回顾
3.1 第一反应:是不是 MOC 没生效?
○ 检查了 CtmAboutUi
类,Q_OBJECT
有,public slots:
有,语法没问题。
○ 打开 Qt 生成的 moc_CtmAboutUi.cpp
一看,MOC 确实生成了,subAppInfo(QVariant)
也注册进去了,说明元对象系统这块是没有问题的。
3.2 第二反应:是不是 Q_ARG 写错了?
○ 一开始用了 Q_ARG(const QVariant&, msg)
,这是个经典坑。
○ Qt 的 Q_ARG
第一个参数是类型名,不能带 const
和 &
(否则元对象系统会直接按照 const QVariant&
去匹配字符串,实际上元对象系统中注册的是QVariant
类型,并没有const QVariant&)
,应该写成 Q_ARG(QVariant, msg)
。
○ 改了之后,错误还在,但变成了 (QVariant)(QVariant)
,说明问题没完。
3.3 第三反应:名字到底传了啥?
cpp
eventBus.subscribe(topic, m_pAboutUi, SLOT(subAppInfo(QVariant)));
○ 这里 SLOT(...)
宏展开后是 "subAppInfo(QVariant)"
,是个带参数列表的字符串。
○ 而 invokeMethod
拿到这个字符串后,会把它当"方法名
"去查,再配上 Q_ARG(QVariant, msg)
,就变成了"subAppInfo(QVariant)
" + "(QVariant)
",即下面这样:
cpp
subAppInfo(QVariant)(QVariant)
这当然找不到,因为实际注册的是 subAppInfo(QVariant)
四、✅ 最终解决办法
把调用方式从:
cpp
eventBus.subscribe(this->topic(), m_pAboutUi, SLOT(subAppInfo(QVariant)));
改成:
cpp
eventBus.subscribe(this->topic(), m_pAboutUi, "subAppInfo");
只传方法名,不带参数列表。这样 invokeMethod
就会用方法名 "subAppInfo
" 去找,再根据 Q_ARG(QVariant, msg)
匹配参数类型,完美匹配成功。
4.1 其它疑问:
我也尝试了如下方法:
虽然 "subAppInfo"
能解决问题,但更推荐用 函数指针 的方式,既安全又现代:
cpp
eventBus.subscribe(this->topic(), m_pAboutUi, &CtmAboutUi::subAppInfo);
这需要 CbbEventBus 支持模板,但好处是:
● 编译时检查,名字写错直接报错
● 不用拼字符串,不怕类型不匹配
● IDE 能跳转,维护方便
但是有错误,没有成功...
五、📝 总结
● QMetaObject::invokeMethod: No such method
不一定是方法不存在,很可能是 名字传错了。
● SLOT()
宏返回的是带参数的字符串,不适合直接传给 invokeMethod
当方法名用。
● Q_ARG
只写类型名,别带 const
和 &
。
● 个人认为最稳妥的方式是用函数指针 &Class::method
,但是该方法没有成功,由于工作时间问题,目前还没继续深究...