3

I have a puzzling issue when trying to apply the Qt meta-object compiler (moc) to a header file in a Qt project. I am using Visual Studio 2013, and performing the moc step using a Custom Build Tool which was orignally auto-generated using the Qt Visual Studio add-in.

First, some tedious but highly relevant background. My project involves streaming data from a camera, which may be any one of a number of different types, into a GUI. Some classes are only relevant if certain camera SDKs are available on the particular tool where I am compiling the code; some classes are relevant if any one of a number of possible camera SDKs are available, but should not be compiled if no SDKs are. To handle this, I have a universal header called "Support.h" which currently consists of the following code:

#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

Every affected .h file and .cpp file then has the structure

#include "Support.h"
#ifdef CAMERA_SUPPORT

... the whole file contents

#endif

In general, this works a treat: if I have a suitable camera SDK present and I compile a single .cpp in isolation, then everything compiles cleanly and I see "Support for at least one camera is available" messages thrown out in all the right places.

However, if I try to build the whole project then I get a bunch of linking errors because none of the meta-object functions required for Qt signals and slots are being generated. On further investigation, I find that although the .h files are being moc'ed just like they should be, the moc compiler is outputting the error message "Note: No relevant classes found. No output generated." each time and the moc_[class name].cpp files, although they are all being generated, are empty!

Going back to the header files and manually replacing '#include "Support.h"' with '#define CAMERA_SUPPORT' solves the problem at a stroke, but utterly defeats the object of what I am trying to achieve.

It seems that what's happening is this:

  1. When a .cpp file is compiled, the preprocessor parses the contents of "Support.h", defines CAMERA_SUPPORT, and thereafter all the relevant blocks of code are seen and compiled correctly.
  2. However, when a .h file is moc'ed, "Support.h" is ignored and CAMERA_SUPPORT remains undefined. However, the preprocessor still strips out everything between the '#ifdef CAMERA_SUPPORT' and the '#endif' so what the moc actually sees is an empty file.

So, in one line, here is my question: why, when the file is moc'ed, is the preprocessor being invoked to check whether CAMERA_SUPPORT is defined, but not to check whether CAMERA_SUPPORT should be defined?

[N.B. I have also tried replacing the line '#include "Support.h"' with the contents of Support.h, and it makes no difference. Therefore it seems that the problem during moc'ing is that the "#if defined(..." line is not being executed, not that it is failing to include Support.h.]

Eos Pengwern
  • 1,467
  • 3
  • 20
  • 37
  • Do either of your pragma messages get printed? – AndyG Jun 02 '16 at 23:20
  • @AndyG No they don't, when running the moc, but see my correspondence with Benjamin T below; it looks like when the moc is preprocessing the file itself it ignores the pragmas. – Eos Pengwern Jun 03 '16 at 06:24

3 Answers3

3

Qt moc tool does not use preprocessed (by the C++ preprocessor) .h files, but directly your .h files and does its own preprocessing.

Make sure that when moc is executed it haves all the information about your DEFINES and include directories (See doc). It could be that it is missing a define.

As an alternative you could use the Q_MOC_RUN define, but I wouldn't recommend it.

Benjamin T
  • 8,120
  • 20
  • 37
  • Thank you for that insight; I tried another experiment, and added an explicit "#define PTGREY_SUPPORT" into the first line of Support.h, rather than relying on it getting the definition from the Visual Studio project file. It worked as expected. Therefore, the real problem here is not that the preprocessing isn't happening, but that the moc is not seeing the same global #defines as the preprocessor is. I may be able to solve this by tweaking the command line used to run the moc from within the Visual Studio project Custom Build Tool; I'll look into that later on today. – Eos Pengwern Jun 03 '16 at 06:27
2

From the documentation (emphasis mine):

Another limitation is that moc does not expand macros, so you for example cannot use a macro to declare a signal/slot or use one to define a base class for a QObject.

[...] Since moc doesn't expand #defines, type macros that take an argument will not work in signals and slots. Here is an illegal example:

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

class Whatever : public QObject
{
    Q_OBJECT

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

A macro without parameters will work.

However, it appears that this is slightly different in QT5, although I don't think it will work for you anyway (from this discussion):

moc is smarter in Qt 5: it expands macros.

But the recommendation stands: do not #if (of any kind) anything that isn't #defined in the same source, in a header next to the one being compiled, or passed as -D in the command-line.

Another good discussion from Thiago Macieira I think shows pretty concretely that hiding anything behind #if is not a good idea:

Right, so let me be clearer: moc in Qt 4 does not expand macro calls.

$ 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: Note: No relevant classes found. No output generated.

AndyG
  • 39,700
  • 8
  • 109
  • 143
1

OK, I think I've figured this out.

I mentioned in my question that the custom build tool that I was using had originally been auto-generated by the Qt Visual Studio add-in. It had the form:

"$(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"  

Those various preprocessor macros following the '-D's were the ones defined at the time when the file was originally created, at a time when I had the add-in installed. They do not include any changes to the list of global macros that I've made since then. If I manually edit the custom build tool to include my new macros:

"$(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"

... then everything works exactly as it should.

So, my problem is really a Visual Studio one - is there a way of taking all the preprocessor defines in the project (collectively denoted %(PreprocessorDefinitions) by Visual Studio) and applying them to a command line in the custom build tool? I don't expect to be able to get an answer to that right here, but I shall look into it...

Eos Pengwern
  • 1,467
  • 3
  • 20
  • 37