Dec 22, 2008

Compiling Qt apps in Windows with MinGW

I love Qt. I haven't actually used it much, as I haven't written many programs, but I've seen enough stuff written about it in developer blogs to have a fanboyish appreciation for the cross-platform toolkit. But aside from installing KDE for Windows back in it's beta days, I hadn't had the opportunity to use the "cross platform" bit until yesterday. I needed to compile screenie for a Windows environment. I knew what I was getting into, having compiled C and C# programs in the past. (I'm a big fan of interpreted languages; compiling always seems to waste hours of development time and involve enormously frustrating obscure compiler and linker errors.) But I loved learning about the process, and particularly discovering that I was capable of overcoming all my compiler errors with my current knowledge and Google. I got to understand Qt (and Windows executables, for that matter) on a more intimate level, solidify in my mind what .dll's are, and what dynamic and static linking are. After multiple attempts, I ended up with a standalone executable that ran perfectly on XP and Vista. It's an amazing feeling, knowing you started with hundreds, maybe thousands of text files - barely contained chaos! - and ended with a single file - pure simplicity. Plus, I may be the first person to compile screenie for Win32, making my work useful to others. Hopefully the author will make it available on screenie's website.

And now for the boring stuff if you're reading this for the human interest aspect; or the interesting part if you're reading this for the "How To" aspect. After reading several articles about cross-compiling Qt, I decided not to attempt that, and just did the whole process in Windows XP.

How to compile a C++ Qt4 application for Windows
Obviously, these directions are specific to compiling screenie. If you're using this as a guide for compiling something else, use your imagination as needed to adjust the steps.
Where applicable, I specify the generic download page for each application, as well as the specific binary version I used.
1. Download and install Qt4 with MinGW
(http://trolltech.com/downloads/opensource/appdev/windows-cpp)
I used version: ftp://ftp.trolltech.com/qt/source/qt-win-opensource-4.4.3-mingw.exe
Since the source for screenie is a Git repository, I figured we need git. Update 2008-12-26: There is a Download button on the screenie Github page to download the source as a zip file. Extract it to C:\screenie and jump to step 5.
2. Download and install msysgit
(http://code.google.com/p/msysgit/downloads/list)
I used version: http://msysgit.googlecode.com/files/Git-1.6.0.2-preview20080923.exe
3. Run Git Bash and execute git clone git://github.com/ariya/screenie.git
4. Copy the downloaded source directory to c:\screenie
5. Open the Qt 4.4.3 Command Prompt from the start menu.
It's like a normal command prompt, except with some environment variables and bash scripts set. (For instance, it appears to alias make to mingw32-make, which saves typing.) It is assumed you are using the Qt Command Prompt for all the steps below involving a prompt.

By default Qt installs itself for compiling with dynamically linked libraries. This means you have to copy the compiled executable, as well as a bunch of .dll's to each computer you install it on. It's not really a problem: just put them all in a zip file, unzip it on the Desktop, and it runs nicely. But if you're like me, and want a single executable with no dependencies, scroll down to the statically linked bit.

Dynamically linked (.dll dependencies, distribute as .zip file)
6. cd C:\screenie
7. Issue qmake -spec "C:\Qt\4.4.3\mkspecs\win32-g++"
For some reason, qmake seemed confused out-of-the-box, and I had to fix several things in the makefile. I used the Find and Replace feature in Crimson Editor (my favorite Windows text editor.)
8. In Makefile.Release replace:
"iwmake\build_mingw_opensource" with "Qt\4.4.3"
(There were 8 instances)
"QtGui" with "QtGui4"
"QtCore" with "QtCore4"
(Both in the LIBS line)
The same substitutions would be needed to fix Makefile.Debug, except instead of QtGui4 and QtCore4 it's the debug versions QtGui4d and QtCore4d. But you don't need to build the debug version anyway.
9. Issue make.
The compiled executable appears in C:\screenie\release. Test the program and see if it works. For me, Screenie worked on Windows! Yea!

Following the advice on the Trolltech website, I used Dependency Walker to find what .dll's the executable requires. I copied those .dll's to the release directory. In screenie's case, Dependency Walker listed QtCore4.dll, QtGui4.dll, msvcrt.dll, and kernel.dll. Kernel.dll is already installed on every windows system so no need to distribute it. Msvcrt.dll is the Microsoft C Runtime Library - probably installed on people's computers, but I included a copy anyway, along with QtCore4.dll and QtGui4.dll. I had to include one more .dll that Dependency Walker did not find, a copy of c:\MinGW\mingwm10.dll. (See note in static compiling.) Also, any plugins used need to be copied to the release folder. In this case, screenie needs the image plugins to read JPEG or GIF files, so copy the contents of c:\Qt\4.4.3\plugins\imageformats to c:\screenie\release\imageformats. You can delete the .dll's whose names end in the letter "d"; they are debug versions and take up a lot of space, and are not used if you compile for release. Copy the release folder to the Desktop, give it a better name (like "screenie"), zip it, and voila! Copy it to another computer and hopefully it will run.

Statically linked (no dependencies, single file to distribute)

To my dismay, compiling statically was not simple. To begin, we must recompile Qt itself.
6. Using the Qt 4.4.3 Command Prompt from the start menu...
cd C:\Qt\4.4.3
configure -static
This takes a long time. I read later to do a distclean before rebuilding... but I didn't and it built OK. However, I wonder if this is why later qmake decided to link to the old .dll's ending in 4.
7. make sub-src
This takes a really long time. (By this point it was past 3:00 am, so I left it running while I slept.)

When Qt is statically linked, it cannot dynamically load plugins. (Turns out it's an artificial restriction that's easily overwritten.) But we want those image plugins statically compiled into our final executable. To make that happen you have to do the following voodoo. Luckily it's straight from the Trolltech documentation.
8. Add Q_IMPORT_PLUGIN() statements for each plugin to the c++ code.
I added the lines:
Q_IMPORT_PLUGIN(qjpeg)
Q_IMPORT_PLUGIN(qgif)
to screenie.cpp

(There are plugins for some other image formats, like .tiff and .ico but I didn't think most people would need more than .jpeg, .png, and .gif. PNG support is built into Qt so it doesn't have a plugin.) These lines of code are necessary to compile it statically, but I don't know yet if those lines cause trouble if you try to compile the code dynamically.
9. Add a QTPLUGIN line to the .pro file
I added the lines
QTPLUGIN += qjpeg qgif
CONFIG += static
to screenie.pro
(The CONFIG one is a guess... I actually got it from a page on compiling custom plugins, but figured we want all things static, so why not throw it in? Not gonna make them less static.) Voodoo done.
10. cd C:\screenie
make clean
qmake -config release
This time, it seems to not have the iwmake\build_mingw_opensource problem, but it does need for the 4 from the library names to be deleted. When compiling statically, the library names used do not have the 4 in them. I'd love to know why Trolltech thought that was necessary. (Funny how the 4's weren't there when I needed them, and now are there when I don't want them!)
11. In Makefile.Release, in the LIBS line, replace -lqjpeg4 and -lqgif4 with -lqjpeg and -lqgif.
Delete mthreads because it's an unneeded dependency on mingw10.dll which isn't compiled statically. Apparently Qt doesn't use it, so I don't know why qmake adds it by default. If you don't delete -mthreads, you get an error saying mingw10.dll can't be found when you run it on other computers.
12. Delete all instances of -mthreads in Makefile.Release
13. Now back in C:\screenie issue make
cd release
You should now have a statically compiled executable c:\screenie\release\screenie.exe. See if it works! If everything went according to plan, you can run that file on any Windows XP / Vista computer and use Ariya Hidayat's awesome screenie!

But no! You're a perfectionist. You want more. The screenie.exe file is about 10 MB. Let's see if we can shrink that! I got these tips from http://www.qtforum.org/post/85635/static-compiling.html#post85635
14. From the release folder, issue strip screenie.exe
This is a command from Qt that removes unnecessary parts of Qt from statically compiled files. Sadly, this didn't reduce the file size much, if any. (Perhaps Qt does that behavior by default now.) But there's another thing you can do to reduce file size.
15. Download and install the Ultimate Packer for eXecutables (http://upx.sourceforge.net/)
I used version: http://upx.sourceforge.net/download/upx303w.zip
I had to manually unzip it to C:\Program Files and add it's directory to Window's Path environment variable.
16. Open cmd and cd to c:\screenie\release
Issue upx screenie.exe
Now it is only 3.58 MB. Sweet!