問題如題目所示:QMetaObject::invokeMethod: No such method xxxx
,在網上好一頓查,又將查到的資料喂給了 Ai,才最終將問題解決,特此記錄下。
一、問題背景
在做公司項目時,使用了插件的方式開發。主程序加載了一個叫CtmAboutAppPlugin
的插件。該插件有個界面類CtmAboutUi
,它有個槽函數:
public slot:void subAppInfo(const QVariant& msg);
主程序通過一個叫 CbbEventBus
的事件總線機制,把消息發送給CtmAboutAppPlugin
這個插件。
此處我給出CbbEventBus
的訂閱方法的源碼(問題出在了訂閱方法這里):
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 第三反應:名字到底傳了啥?
eventBus.subscribe(topic, m_pAboutUi, SLOT(subAppInfo(QVariant)));
○ 這里 SLOT(...)
宏展開后是 "subAppInfo(QVariant)"
,是個帶參數列表的字符串。
○ 而 invokeMethod
拿到這個字符串后,會把它當“方法名
”去查,再配上 Q_ARG(QVariant, msg)
,就變成了"subAppInfo(QVariant)
" + “(QVariant)
”,即下面這樣:
subAppInfo(QVariant)(QVariant)
這當然找不到,因為實際注冊的是 subAppInfo(QVariant)
四、? 最終解決辦法
把調用方式從:
eventBus.subscribe(this->topic(), m_pAboutUi, SLOT(subAppInfo(QVariant)));
改成:
eventBus.subscribe(this->topic(), m_pAboutUi, "subAppInfo");
只傳方法名,不帶參數列表。這樣 invokeMethod
就會用方法名 “subAppInfo
” 去找,再根據 Q_ARG(QVariant, msg)
匹配參數類型,完美匹配成功。
4.1 其它疑問:
我也嘗試了如下方法:
雖然 "subAppInfo"
能解決問題,但更推薦用 函數指針 的方式,既安全又現代:
eventBus.subscribe(this->topic(), m_pAboutUi, &CtmAboutUi::subAppInfo);
這需要 CbbEventBus 支持模板,但好處是:
● 編譯時檢查,名字寫錯直接報錯
● 不用拼字符串,不怕類型不匹配
● IDE 能跳轉,維護方便
但是有錯誤,沒有成功…
五、📝 總結
● QMetaObject::invokeMethod: No such method
不一定是方法不存在,很可能是 名字傳錯了。
● SLOT()
宏返回的是帶參數的字符串,不適合直接傳給 invokeMethod
當方法名用。
● Q_ARG
只寫類型名,別帶 const
和 &
。
● 個人認為最穩妥的方式是用函數指針 &Class::method
,但是該方法沒有成功,由于工作時間問題,目前還沒繼續深究…