解读QT信号与槽机制里 QMetaObject::connectSlotsByName(QObject *o)的源码

本文探讨了Qt中connectSlotsByName函数的工作原理,该函数用于根据objectName自动连接信号与槽。通过实验发现,当存在多个具有相同objectName的对象时,仅首个对象的信号会被连接。文章深入分析了源码,揭示了这一行为的原因,并提出了最佳实践。


介绍

connectSlotsByName 是一个QMetaObject类里的static函数,其定义如下:

static void connectSlotsByName(QObject *o);

其作用是如其名称一样,用来将QObject *o里的子孙QObject的某些信号按照其objectName连接到o的槽上。

起因

为啥会对这个函数产生一探究竟的想法呢?——

既然是根据objectName来连接信号和槽,那么就有了几个问题:

  1. 能不能对多个QObject设置同样的objectName呢?
  2. 如果能,那么connectSlotsByName会连接多少个QObject的信号到指定的槽上呢?

测试结果

有了疑问,第一个应该做的事情,当然是编写代码进行测试了。

在测试的主窗口类构造函数在“ui->setupUi(this); ”语句前编写如下代码:

     for ( int  i = 0 ;i < 9 ; ++ i)
    {
        QPushButton 
* btn = new  QPushButton( this );
        btn
-> setObjectName(“TestButton”);
        qDebug(btn
-> objectName().toStdString().c_str());
    }

    ui
-> setupUi( this );

QMetaObject::connectSlotsByName()这个函数会在ui->setupUi(this);里被调用执行。

然后在主窗口里增加下面的槽定义很代码:

 

     void  on_TestButton_clicked( bool  bVal);

    
void  MainWindow::on_TestButton_clicked( bool  bVal)
    {
        QObject 
* obj = sender();
        qDebug(
" TestButton is clicked by %s!%d/n " ,obj -> objectName().toStdString().c_str(),bVal);
    }

 

然后编译运行,结果出来了:

  1. 9个按钮的objectName()都返回"TestButton"
  2. 只有第一个按钮的clicked信号被连接到了on_TestButton_clicked槽上

第一个结论与我的猜想相符(后来看了QObject的源码,也是比较简单的),第二个结论与我的猜想有点不同,我本来猜想,应该是9个按钮的 clicked信号应该都可以连接到这个on_TestButton_clicked槽上的,但是却只有第一个连接上了,这是为什么呢?

让我们看看connectSlotsByName都干了些什么吧。

connectSlotsByName的源码解读

 

1 void QMetaObject::connectSlotsByName(QObject *o)
2 {
3     if (!o)
4         return;
5     const QMetaObject *mo = o->metaObject();
6     Q_ASSERT(mo);
7     const QObjectList list = qFindChildren<QObject *>(o, QString());
8     for (int i = 0; i < mo->methodCount(); ++i) {
9         const char *slot = mo->method(i).signature();
10         Q_ASSERT(slot);
11         if (slot[0] != 'o' || slot[1] != 'n' || slot[2] != '_')
12             continue;
13         bool foundIt = false;
14         for(int j = 0; j < list.count(); ++j) {
15             const QObject *co = list.at(j);
16             QByteArray objName = co->objectName().toAscii();
17             int len = objName.length();
18             if (!len || qstrncmp(slot + 3, objName.data(), len) || slot[len+3] != '_')
19                 continue;
20             const QMetaObject *smo = co->metaObject();
21             int sigIndex = smo->indexOfMethod(slot + len + 4);
22             if (sigIndex < 0) { // search for compatible signals
23                 int slotlen = qstrlen(slot + len + 4) - 1;
24                 for (int k = 0; k < co->metaObject()->methodCount(); ++k) {
25                     if (smo->method(k).methodType() != QMetaMethod::Signal)
26                         continue;
27
28                     if (!qstrncmp(smo->method(k).signature(), slot + len + 4, slotlen)) {
29                         sigIndex = k;
30                         break;
31                     }
32                 }
33             }
34             if (sigIndex < 0)
35                 continue;
36             if (QMetaObject::connect(co, sigIndex, o, i)) {
37                 foundIt = true;
38                 break;
39             }
40         }
41         if (foundIt) {
42             // we found our slot, now skip all overloads
43             while (mo->method(i + 1).attributes() & QMetaMethod::Cloned)
44                   ++i;
45         } else if (!(mo->method(i).attributes() & QMetaMethod::Cloned)) {
46             qWarning("QMetaObject::connectSlotsByName: No matching signal for %s", slot);
47         }
48     }
49 }

 

看connectSlotsByName的实现,可以注意到以下几个地方:

  1. 第7行,取得o的所有子对象,在测试的代码里,QPushButton都设置了this为父对象,所以它们显然会在这个列表里出现
  2. 第8行,是一个遍历o的方法的循环,o的信号和槽就在其中
  3. 第11行,对于方法名称不是"on_"开头的方法跳过不处理,这也说明,如果你在一个QObject子类里定义了"on_"开头的槽的话,一定会被connectSlotsByName函数进行搜索匹配的操作的
  4. 第14行开始到33行,开始遍历o的所有的子对象,试图匹配到与槽名称以及信号名称相应的子对象。首先取出其objectName()与槽名称里的第一个‘_’和第二个‘_’做名称匹配。其次取出子对象的所有信号,与第二个‘_’之后部分做匹配。
  5. 如果匹配成功,则会执行36行的连接代码。连接成功的话,就会在38行break中断循环。

看到第5点,已经很明了了,对于同名的控件,connectSlotsByName只会连接子对象链表里的第一个对象的信号到槽上。

总结和其他

 

做个小小的总结:

  1. 尽量不要让QObject出现相同objectName的情况
  2. 如果同名connectSlotsByName只能给其中一个建立缺省的信号和槽的连接
  3. 如果出现大量编码创建大量控件的情况,最好是自己去建立信号和槽的连接,而不是依赖connectSlotsByName来做到这个工作。connectSlotsByName更适合的任务是与desinger配合完成缺省的信号和槽的连接。

其他:

在测试过程中,曾经把ui->setupUi(this);放到了控件创建之前运行,结果运行时提示:

QMetaObject::connectSlotsByName: No matching signal for on_TestButton_clicked

从connectSlotsByName的代码可以看到这实际上执行的是第46行,如果在调试程序中遇到这样的信息,可以检查一下,是否是控件的objectName与你编写的槽里的objectName并不相符。

<think>我们正在处理一个链接错误,具体是关于`libStereoCamera.so`中未定义的`QMetaObject`引用,特别是`invokeMethodImpl`。错误信息表明在ARM64架构上使用Qt5时出现了问题。根据引用[2]中的信息,这种错误通常是由于编译的库和应用程序不匹配造成的。可能的原因包括:1.使用的Qt库版本应用程序编译时使用的版本不一致。2.编译的架构(如x86x64,或者这是ARM64)不匹配。3.链接的库文件(.so或.a)不是针对目标平台编译的。解决方案步骤:1.**确认Qt版本和架构匹配**:确保你的应用程序和所有依赖的库(包括`libStereoCamera.so`)都是使用相同版本的Qt和相同的架构(ARM64)编译的。2.**清理和重新构建**:有时构建过程中可能出现残留文件,导致链接了错误的库。尝试清理项目并重新构建。```bashmakecleanmake```3.**检查Qt库的路径**:确保在链接时使用的是正确的Qt库路径。在ARM64平台上,应该链接到ARM64编译的Qt库。4.**检查链接命令**:在链接`libStereoCamera.so`时,确保它链接了正确的Qt库。你可以通过检查链接命令中的`-L`(库路径)和`-l`(库名称)选项来确认。5.**使用QMetaObject的符号问题**:在Qt中,`QMetaObject`的某些方法可能在不同的模块中。`invokeMethodImpl`是`QMetaObject`内部使用的方法,通常不会直接调用。如果错误发生在你自己的代码中,请检查是否正确包含了相关的头文件,并且是否正确使用了`Q_INVOKABLE`或`slots`等宏。6.**检查Qt模块的链接**:确保在项目文件(如.pro文件)中正确指定了所需的Qt模块。例如,如果使用了核心模块,则应有:```qmakeQT+=core```7.**交叉编译环境配置**:如果是在交叉编译环境下(例如在x86主机上编译ARM64目标程序),请确保你的交叉编译工具链配置正确,并且Qt的交叉编译版本已正确安装。8.**检查依赖库的编译选项**:如果`libStereoCamera.so`是第三方库,请确认该库是否针对ARM64架构和你的Qt版本编译。如果不是,你需要获取或编译一个匹配的版本。9.**查看符号表**:使用`nm`工具检查`libStereoCamera.so`中是否缺失了`QMetaObject`的符号,以及检查Qt库中是否包含该符号。例如:```bashnm-DlibStereoCamera.so|grepQMetaObjectnm-D/path/to/Qt/lib/libQt5Core.so|grepinvokeMethodImpl```10.**重新安装Qt**:如果怀疑Qt库本身有问题,可以尝试重新安装针对ARM64的Qt版本。根据错误信息,特别关注`_ZN11QMetaObject14new_metaobjectEPKcS1_P9QMetaDataiS3_iP10QClassInfoi`这个符号,它可能是由于Qt元对象系统在链接时找不到定义。这通常是因为没有正确链接QtCore库或者库版本不匹配。请按照上述步骤逐一检查,特别是确保所有组件(应用程序、库、Qt)都在同一架构(ARM64)和同一Qt版本下编译。如果问题仍然存在,请提供更多关于你的构建环境(如Qt版本、编译器、操作系统等)和构建过程的信息。§§相关问题§§1.如何检查一个动态链接库的依赖项?2.在交叉编译Qt应用程序时需要注意哪些关键点?3.如何解决Qt中常见的链接错误?</think>### 解决链接错误:未定义的 QMetaObject 引用 当在 ARM64 架构上链接 `libStereoCamera.so` 时出现 `undefined reference to QMetaObject::invokeMethodImpl` 错误,通常是由于 **Qt 库版本/架构不匹配****元对象系统未正确初始化** 导致。以下是系统级解决方案: #### 1. 检查 Qt 库架构一致性(关键步骤) ```bash # 检查已安装的 Qt 库架构 file /path/to/Qt/lib/libQt5Core.so.5 # 预期输出应包含 "ARM aarch64" 字样 # 若显示 "x86" 或 "ARM 32-bit",则架构错误 ``` #### 2. 验证编译环境配置 在项目配置文件(`.pro` 或 `CMakeLists.txt`)中显式指定 ARM64 架构: ```qmake # Qt .pro 文件示例 QT += core CONFIG += arm64 QMAKE_TARGET_ARCH = arm64 ``` #### 3. 强制重新生成 MOC 文件 删除现有元对象代码(MOC)并重新生成: ```bash rm -f moc_*.cpp qmake -makefile make clean make ``` #### 4. 检查符号链接(针对动态库) ```bash # 验证符号存在性 nm -D libStereoCamera.so | grep QMetaObject::invokeMethodImpl # 若缺失符号,重新编译时添加调试信息 qmake CONFIG+=debug ``` #### 5. 运行时库路径配置 在 ARM64 设备上设置正确的库路径: ```bash export LD_LIBRARY_PATH=/opt/Qt-arm64/lib:$LD_LIBRARY_PATH ./your_application ``` #### 根本原因分析 此错误通常由以下原因引起: 1. **架构不匹配**:x86 Qt 库链接到 ARM64 应用(如引用[2]所述) 2. **MOC 生成失败**:类声明未使用 `Q_OBJECT` 宏 3. **ABI 不兼容**:不同编译器版本生成的二进制文件(如 GCC Clang) 4. **链接顺序错误**QtCore 库未在依赖链中首位链接 > **验证成功的关键指标**:编译后执行 `ldd libStereoCamera.so` 应显示所有 Qt 库路径指向 ARM64 版本[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值