在 Qt 中移动头文件时出现奇怪的预处理器行为

Posted

技术标签:

【中文标题】在 Qt 中移动头文件时出现奇怪的预处理器行为【英文标题】:Strange preprocessor behaviour when moc'ing a header file in Qt 【发布时间】:2016-06-02 22:59:23 【问题描述】:

在尝试将 Qt 元对象编译器 (moc) 应用于 Qt 项目中的头文件时,我遇到了一个令人费解的问题。我正在使用 Visual Studio 2013,并使用自定义构建工具执行 moc 步骤,该工具最初是使用 Qt Visual Studio 插件自动生成的。

首先,一些乏味但高度相关的背景。我的项目涉及将来自相机的数据(可能是多种不同类型中的任何一种)流式传输到 GUI 中。某些类仅在我正在编译代码的特定工具上可用某些相机 SDK 时才相关;如果任何一个可能的相机 SDK 可用,则某些类是相关的,但如果 没有 SDK 可用,则应该编译。为了处理这个问题,我有一个名为“Support.h”的通用头文件,它目前包含以下代码:

#if defined(PTGREY_SUPPORT) || defined(SVS_VISTEK_SUPPORT)
#define CAMERA_SUPPORT
#endif

#ifdef CAMERA_SUPPORT
#pragma message ("Support for at least one camera is available")
#else
#pragma message ("No support is available for any camera on this machine")
#endif

每个受影响的 .h 文件和 .cpp 文件都有结构

#include "Support.h"
#ifdef CAMERA_SUPPORT

... the whole file contents

#endif

一般来说,这是一种享受:如果我有一个合适的相机 SDK,并且我单独编译了一个 .cpp,那么一切都可以干净地编译,我会看到“支持至少一个相机可用”消息抛出所有正确的地方。

但是,如果我尝试构建整个项目,则会收到一堆链接错误,因为没有生成 Qt 信号和插槽所需的元对象函数。在进一步调查中,我发现虽然 .h 文件正在按照应有的方式进行 moc,但 moc 编译器正在输出错误消息“注意:未找到相关类。未生成输出。”每次生成 moc_[class name].cpp 文件,虽然它们都在生成,但都是空的!

返回头文件并手动将 '#include "Support.h"' 替换为 '#define CAMERA_SUPPORT' 一下子解决了问题,但完全违背了我想要实现的目标。

好像是这样的:

    编译 .cpp 文件时,预处理器会解析“Support.h”的内容,定义 CAMERA_SUPPORT,然后正确查看和编译所有相关代码块。 但是,当一个 .h 文件被 moc'ed 时,“Support.h”被忽略并且 CAMERA_SUPPORT 保持未定义。但是,预处理器仍然会删除 '#ifdef CAMERA_SUPPORT' 和 '#endif' 之间的所有内容,因此 moc 实际看到的是一个空文件。

所以,在一行中,这是我的问题:为什么当文件被 moc'ed 时,预处理器被调用来检查 CAMERA_SUPPORT is 是否定义,而不是检查 CAMERA_SUPPORT 应该定义吗?

[注:我也试过用 Support.h 的 contents 替换 '#include "Support.h"' 行,这没什么区别。因此,moc'ing 期间的问题似乎是“#if defined(...”行没有被执行,而不是它没有包含 Support.h。]

【问题讨论】:

您的任何一条编译指示消息都会被打印出来吗? @AndyG 不,他们没有,在运行 moc 时,但请参阅下面我与 Benjamin T 的通信;看起来当 moc 正在预处理文件本身时它忽略了编译指示。 【参考方案1】:

Qt moc 工具不使用(由 C++ 预处理器)预处理的 .h 文件,而是直接使用您的 .h 文件并进行自己的预处理。

确保当 moc 被执行时,它拥有关于你的 DEFINES 和包含目录的所有信息(参见 doc)。可能是缺少定义。

作为替代方案,您可以使用 Q_MOC_RUN 定义,但我不推荐它。

【讨论】:

感谢您的洞察力;我尝试了另一个实验,并在 Support.h 的第一行中添加了一个显式的“#define PTGREY_SUPPORT”,而不是依赖它从 Visual Studio 项目文件中获取定义。它按预期工作。因此,这里真正的问题不是没有进行预处理,而是 moc 没有看到与预处理器相同的全局#defines。我可以通过调整用于从 Visual Studio 项目自定义构建工具中运行 moc 的命令行来解决这个问题;我会在今天晚些时候对此进行调查。【参考方案2】:

来自documentation(强调我的):

另一个限制是 moc 不扩展宏,因此您不能使用宏来声明信号/插槽或使用宏来定义 QObject 的基类。

[...] 由于 moc 不扩展#defines,带参数的类型宏在信号和槽中不起作用。这是一个非法的例子:

#ifdef ultrix
#define SIGNEDNESS(a) unsigned a
#else
#define SIGNEDNESS(a) a
#endif

class Whatever : public QObject

    Q_OBJECT

signals:
    void someSignal(SIGNEDNESS(int));
;

没有参数的宏可以工作。

然而,这似乎在 QT5 中略有不同,尽管我认为它无论如何都不适合你(来自this discussion):

moc 在 Qt 5 中更智能:它扩展了宏。

但建议是:不要#if(任何类型)任何 不是 #defined 在同一个源中,在正在编译的源旁边的标题中,或在命令行中作为 -D 传递。

Another good discussion from Thiago Macieira 我认为非常具体地表明,在#if 后面隐藏任何东西不是一个好主意:

好吧,让我更清楚一点:Qt 4 中的 moc 不会扩展宏调用。

$ cat foo.cpp

#define FOO(x) x
#if FOO(1) class Foo : public QObject  Q_OBJECT ;
#endif

$ moc -qt4.8 foo.cpp > /dev/null

foo.cpp:0:注意:未找到相关类。没有生成输出。

【讨论】:

【参考方案3】:

好的,我想我已经想通了。

我在我的问题中提到,我使用的自定义构建工具最初是由 Qt Visual Studio 插件自动生成的。它的形式是:

"$(QT32)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\moc_%(Filename).cpp"  -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_THREAD_SUPPORT -DQT_CORE_LIB "-I$(QT32)\include" "-I.\GeneratedFiles\." "-I $(QT32)\include\qtmain" "-I $(QT32)\include\QtCore"  

'-D's 后面的各种预处理器宏是在最初创建文件时定义的,当时我安装了加载项。它们不包括我从那时起对全局宏列表所做的任何更改。如果我手动编辑自定义构建工具以包含我的新宏:

"$(QT32)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\moc_%(Filename).cpp"  -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_THREAD_SUPPORT -DQT_CORE_LIB -DPTGREY_SUPPORT -DSVS_VISTEK_SUPPORT "-I$(QT32)\include" "-I.\GeneratedFiles\." "-I $(QT32)\include\qtmain" "-I $(QT32)\include\QtCore"

...然后一切都按原样进行。

所以,我的问题实际上是 Visual Studio 问题 - 有没有办法获取项目中定义的所有预处理器(Visual Studio 统称为 %(PreprocessorDefinitions))并将它们应用到自定义构建工具中的命令行?我不希望能在这里得到答案,但我会调查一下......

【讨论】:

以上是关于在 Qt 中移动头文件时出现奇怪的预处理器行为的主要内容,如果未能解决你的问题,请参考以下文章

使用VS开发QT项目时出现找不到QT头文件

使用VS开发QT项目时出现找不到QT头文件

错误 C2059:尝试在已知大小的头文件中创建 Qt 容器时出现“常量”

PHP curl文件上传::当表单名称为整数时出现奇怪的curl行为

将 Excel 工作表导出到 PDF 文件时出现奇怪的 Powershell 行为

如何在 Visual Studio 中自动将预处理器和注释块插入到新的 C++ 头文件中?