Sunday 28 April 2013

Developing with Qt on the MSVC Express IDE

You can use Qt Creator with the MSVC compiler. However, you may prefer to use the MSVC IDE, instead. This is harder than it sounds, because Qt's build process may have additional compile steps, such as invoking uic or moc; and, by default, MSVC isn't aware of these steps. There is a Qt plugin, but it doesn't work in MSVC Express. So, if you're using MSVC Express, you need a different solution.

Googling provides several answers for this. The one I found more often was using qmake -tp vc to generate a .vcxproj file out of a .pro file. Works like a charm, and you're guaranteed to get a flawless build on MSVC with no extra work. Before running this command, just make sure your PATH includes qmake, set your QMAKESPEC correctly (if you're in doubt about the correct value for your system, check the mkspecs folder in your Qt installation), and... well, you'll have some relative called Bob, I guess.

This will also set all the required include and lib folders automatically; however, it won't set the runtime path, so before you try debugging your app from the MSVC IDE, go to your project's Property Pages -> Configuration Properties -> Debugging -> Environment and set the PATH to include all the required DLLs.

Running qmake like this takes a snapshot of the .pro file and creates a similar .vcxproj file, which means that further changes to your .pro file will require running qmake again. This will generate a new .vcxproj file (amazing, hey?), so you'd better a) have a good merge tool; or b) don't plan on making any changes to the .vcxproj file.

Why is this a problem? Well, even though we're using MSVC, we'll still have to work in Qt Creator, because: 1) Working in Qt Designer is a lot easier than creating .ui files by hand; 2) adding new forms to a Qt Creator's project also sets up the corresponding C++ files; or 3) when using Designer's tools for setting up signals/slots, you must have Qt's project open, if you just double-click the .ui file in your MSVC project, it opens the corresponding form in Qt Designer, but there's some things you won't be able to do (like using the Go to slot... option) because your project isn't open in Qt Creator (i.e., Qt Creator is editing a form, but doesn't know where are the corresponding C++ files, where the code must go). So, using MSVC as your IDE doesn't mean you'll never need to fire Qt Creator again.

We could create our MSVC project like this and then, whenever we added something to our .pro file, we'd manually add it to our MSVC project (and set its Item Type to Custom Build Tool and fill all the other required fields, if necessary).

Well, we could, but this wouldn't be much of a post if there wasn't some sort of alternative, right?

And we do have an alternative. I've first come across it here. Just by using what you find there (.target files for MSBuild), you'll have automatic handling of .ui and .qrc files. MOC compilation is treated on an item-by-item basis. I've looked at this, and decided to make some changes that better fit my planned workflow.

Before we start - everything written below assumes none of your own source files follows these specs: moc_*.cpp, ui_*.h, and qrc_*.cpp. The only files that follow these specs should be the files generated by moc, uic, and rcc. If that's not the case, then do not to use anything that I present/suggest on this post; doing so can not only mess up your build process in MSVC, but can also delete your source files by erroneously assuming they were generated. You've been warned.

moc compiler

My first goal is automating moc.

Note: The post linked above states that "since usually not all headers in a Qt project use the Q_OBJECT macro moc.exe would run and fail for those headers". This might have been correct at the time it was written, but not anymore. If you run moc on a file without the Q_OBJECT macro, this is what you get

>moc sometestclass.h
sometestclass.h(0): Note: No relevant classes found. No output generated.

>echo %ERRORLEVEL%
0


Contrast that with running moc on a non-existent file

>moc mainrrr.cpp
moc: mainrrr.cpp: No such file

>echo %ERRORLEVEL%
1


So, while I could just run every .h and .cpp file through moc, I prefer running just those that actually require it. So, I created a batch script that runs a grep for Q_OBJECT and uses wc -l to count the matches.

for /f "usebackq" %%a in (`grep "Q_OBJECT" %1 ^| wc -l`) do @set ret=%%a
if not %ret% == 0 echo %1 >> %2


Yes, this is on Windows; you can either install these utilities or use another alternative. The two parameters (%1 and %2) are the source file to check for Q_OBJECT and the result file where we'll write the source file's path, if a match is found.

I've then used the .targets file for uic on the linked article and adapted it to work with my script and invoke moc. So, what does it do?
  • It runs on every .h and .cpp file on the project root and its sub-folders, except files that follow the moc_*.cpp spec.
  • For each of these files, it runs the batch script described above.
  • It reads the result file, and runs each source file on the list through moc. moc's output is placed on either the debug or the release folder.
  • It adds the moc-compiled files to ClCompile.
The last step means we don't have to manually add the moc-generated files to our project. Let's look at an example. Suppose we have a mainwindow.h that looks like this:

class MainWindow : public QMainWindow
{
    Q_OBJECT

    etc...
};

We need to run mainwindow.h through moc, which will then create moc_mainwindow.cpp. However, unless moc_mainwindow.cpp is already part of our project, it won't be included in the build. And the build will fail, obviously. This would mean that, for every x file that needed moc, we would have to manually add a moc_x.cpp to our project. Not that much work, yes, but not my cup of tea, either. Especially when we can just let the build sort it out and add it automatically.

There is just one thing I haven't yet figured out - even when this target is skipped, the moc_*.cpp files are still added to the build, which doesn't make sense to me.

uic & qrc compilers

For these, I've made almost no changes to the .targets file on the linked article.
  • I've placed the output files in the debug and release folders.
  • I've added the .h files to ClInclude and .cpp files to ClCompile.

Bringing it all together

So, this is my suggested approach:
  • Create your Qt project, and immediately run qmake -tp vc to create the MSVC project.
  • Open the MSVC IDE, and remove the following files from the project: *.ui files, moc_*.cpp files, ui_*.h files, *.qrc files, qrc_*.cpp files.
  • Check the remaining files properties for any Item Type that is set as Custom Build Tool - e.g., if you have a form, qmake -tp vc will turn your form's .h file (say, mainwindow.h) Item Type to Custom Build Tool and call moc on it (because your form has the Q_OBJECT macro). Change all Item Types to C/C++ header or C/C++ compiler.
At this point, you should have a project with only .h and .cpp files, and there should be no custom build steps. Naturally, if you try to build it in this state, the build will fail, because neither moc nor uic will be called. You'll need to add the rules to call those tools. Right-click the project on the Solution Explorer and select Build Customizations. Click Find Existing... and navigate to the folder where you have the 3 .targets files described above (I've called mine QtMOC.targets, QtUIC.targets, and QtRCC.targets). Add them, and enable them (i.e., check the check-boxes before their names).

If you build now, you should have a successful build.

So, how do I plan to use this setup, then?
  • Changes to the form should be made in Qt Designer, opening the Qt project with Qt Creator.
  • Any UI additions will require us to manually add the .h and .cpp files. There's no need to add the .ui file, it'll be picked up by our rule.
  • Any new C++ source file will have to be manually added.
  • Nothing else needs to be added to the MSVC project. Our rules will pick up all the necessary files and include them in the build.
This is a level of manual work I'm comfortable with, because all it entails is adding C++ files. I'm not worrying about adding any specific Qt files, those are handled automatically.

I'll be using the same folder as project root for both Qt Creator's and MSVC's projects. The generated files will go on different sub-folders, though - I'll use MSVC's default debug and release folders, and for Qt Creator, I'll call them something like QtC-<Kit>-debug and QtC-<Kit>-release.

The .targets files output messages to the build log. However, to see those messages, you have to go to Tools -> Options -> Projects and Solutions -> Build and Run and set both MSBuild project build output verbosity and MSBuild project build log file verbosity to Normal. Alternatively, you can tweak the Importance parameter of the Message tasks.

Clean

You'll notice when you run Build -> Clean Solution, the generated files remain. If you don't want that, there's a simple way to take care of it. Open your project's Property Pages, go to Configuration Properties -> General -> Extensions to Delete on Clean, and select Edit from the combo box. Add the following lines:
moc_*.cpp
ui_*.h
qrc_*.cpp

Yep, even though it says extensions, it actually uses the whole file spec, which is the intelligent thing to do, actually. I'll say it again, obvious though it may be: If any of your own files follow these specs, then you can't use this.

You don't need to worry about dealing with the debug or release folders, because MSVC runs Clean on those folders, depending on which configuration you have active - if you copy a moc_*.cpp file and put it on your project's root folder, or on any other sub-folder, it won't be deleted.

There's an alternative to this, which is to place all the generated files in one folder (e.g., generated_files), and adding something like this:
generated_files\moc_*.cpp
generated_files\ui_*.h
generated_files\qrc_*.cpp

This means you'd have to create this folder in both debug and release folders. If you wanted a single generated_files folder, under your project's root folder, you'd have to add "..\" to each of the 3 lines above.

A final note

I'm just learning this, and none of the above has been thoroughly tested. Hence, the disclaimers on the .targets files. Use this at your own risk.

You can get the files here.

No comments:

Post a Comment