Cross-compiling GTK Applications for Windows from GNU/Linux

Moral of the story: use Fedora instead of Ubuntu if you want to build GTK applications for Windows without using Windows. Also, don't try to emulate Windows. You don't need Wine until the build phase is over and you are into testing.

UPDATE on 2023-06-20: If you are looking for a tutorial, someone else has done an excellent job. Detials and link in the end.

Okay, short story long. I have been working on a drawing (or digital painting) application for past couple of weeks (which is a story in itself), made possible by something else I cooked up (longer story). It's of course developed on GNU/Linux, but I've wanted to make it available on as many Desktop operating systems as possible from the very beginning. That includes Windows and macOS [1].

Thanks to the hard work of people behind projects like MinGW and Wine, it's possible to develop for Windows without using Windows. I've experimented with this in the past, and even released a version (now outdated) of Theeram for Windows. I could only test it with Wine, and it was surprising when somebody tested it on actual Windows and informed me that it worked (although the look and feel was terrible, which need not be the case always).

Partial Success on Ubuntu

It'd been at least five years since I'd tried compiling for Windows from GNU/Linux. I didn't remember much of the details, so it was almost a fresh start. The corresponding page on the official website of GTK assumes you are using Windows if you want to build for Windows, so my initial strategy was to use Wine and follow the same instructions. The backup plan was to use ReactOS to do the same.

But I couldn't get any of MSYS2, MinGW or Cygwin running on Wine or ReactOS. I tried multiple versions, both x86 and x64, and each time something was wrong. Some of it used to work in the past, but not anymore (nothing new; look it up). I want to keep this post short, so no details here.

Then it occured to me that MinGW gcc is available for GNU/Linux and one could cross-compile for Windows that way, without even using Wine (well, if you use Wine, it's technically not a cross-compilation; this is the real one).

I was on Ubuntu. So I installed mingw-w64 using apt and tried these successfully:

  1. Compile a simple console-based Hello World program using x86_64-w64-mingw32-gcc-win32 and run it with wine64 [2]
  2. Compile and run a Win32 API example the same way [3]
  3. Compile and run a GTK Hello World program the same way, depending on a pre-built GTK bundle available on SourceForge for headers and DLLs. [4][5]

(See footnotes for details and tips.)

With the third step, I was closer to the solution. It wasn't the solution because the bundle was for a very old version of GTK. Also, it needs additional effort to verify the authenticity of such bundles.

MinGW has the latest GTK/GLib packages pre-built, but there is a lot of them and managing the dependencies manually is hard without MSYS2. So I continued the search and reached a GNOME forum thread (well, I don't remember extactly when did I reach there, but this order fits the story well). In that thread, two developers from the GNOME Team make it clear that Fedora ships packages needed for cross-compiling with GTK, and Debian or Ubuntu do not.

Fedora: How It's Done

Simpler than you can imagine. I'm not sure if you can build GTK apps for Windows on Windows as easy. Following is what I did for test building my drawing application. (The code is in C, uses GTK3, and has a simple Makefile-based build system. It was Fedora Linux 38 Workstation Edition, 64-bit.)

  1. Install mingw64-gcc and mingw64-gtk3 (along with make and pkg-config) via dnf. (It only took one command.)
  2. Update the Makefile so that the build process will use the Windows-targeting gcc and it'll look for the headers and DLLs provided by MinGW. (Just three variables.)
  3. Build it without Wine and then test it using Wine. (Again, one command each.)

Now to the details. Here goes the installation part:

$ sudo dnf install mingw64-gcc mingw64-gtk3 make pkg-config wine

Now the second step. I had a typical Unix-targeting Makefile that started like this:

CC=gcc
CFLAGS=$$(pkg-config --cflags --libs gtk+-3.0)
LDFLAGS=$$(pkg-config --libs gtk+-3.0)

This is how the modified values look (you can override the variables while running make instead of editing the Makefile itself):

CC=x86_64-w64-mingw32-gcc
CFLAGS=$$(PKG_CONFIG_PATH=/usr/x86_64-w64-mingw32/sys-root/mingw/lib/pkgconfig/ pkg-config --cflags gtk+-3.0)
LDFLAGS=$$(PKG_CONFIG_PATH=/usr/x86_64-w64-mingw32/sys-root/mingw/lib/pkgconfig/ pkg-config --libs gtk+-3.0)

I couldn't find a MinGW version of pkg-config in the repo [6], but it's easy to repurpose the one for GNU/Linux by making it consult MinGW by setting PKG_CONFIG_PATH. Its value can easily be found with the command locate pkgconfig|grep pkgconfig$

(UPDATE on 2023-06-20: There is mingw64-pkg-config, which I missed somehow. The output seems to differ too, so try it first.)

Then I issued the make command, and it built without even a single error (well, I had to replace a POSIX-specific function, but that's my fault)! When run the output EXE file using Wine, I was blown away because not only did the program work, but looked and felt almost like a GNOME application (well, it may not be desirable on Windows, but you can change the theme).

Screenshot of a GTK-based application compiled for Windows running on Fedora
vara.exe runs on Fedora 38

This is how I ran the EXE file:

$ WINEPATH=/usr/x86_64-w64-mingw32/sys-root/mingw/bin wine vara.exe

(How did I figure out the WINEPATH value? Well, wine had complained that it couldn't find many DLLs, so I selected one of them (libgtk-3-0.dll), and used the locate command to find where it was.)

Testing It on Actual Windows

I don't like the idea of using Windows myself for the actual development process. Thanks to amazing projects like MinGW and Wine, that's not needed either. However, the final product has to be tried on actual Windows, at least to make sure the build process works, and the generated executable isn't malicious. I don't have a Windows installation on any of my personal computers or even on my work computer, so I rented an online virtual desktop instance that runs Windows Server 2022 (based on Windows 10). I don't pretend like I was avoiding proprietary software that way. I was just opting for a more convenient and less intimate way.

Regarding packaging, I was only going for a simple zip file that contains all the necessary files, without any kind of installer. I put my program's EXE files, and unsure of what to include from the GTK side, just copied the whole bin/ directory of MinGW, because that'd worked well as WINEPATH (consider licensing issues when you do that with a public release). Finally, I included a .bat file to set PATH and launch the EXE file.

Screenshot of a GTK-based application running on Windows 10
vara.exe runs on Windows Server 2022 (based on Windows 10)

On Windows, apart from the security warning (which isn't easy for an average home user to override), the application launched fine. All the drawing features worked right (though I couldn't try out pressure sensitivity since I was accessing the virtual machine via a Web browser). The looks were also fine, though non-native.

The program crashed when I tried to open a Save dialog, because gsettings were not found. I read somewhere online that it could be solved by including or setting up a directory. Yet to try that.

What I'm Not Saying

So, what was I saying so for? If you want to build GTK apps for Windows from a free (libre) platform alone, Fedora is the best choice, based on my limited (but not-so-silly) experience. What I'm not saying is that you can cross-compile any GTK-based application like this. If your program even has a single line of platform-specific code that doesn't fit Windows, it's a different story. Even with this successful experiment, I had to replace Unix-specific drand() with another function to get it compiling.

So in summary, building GTK apps for Windows is very easy on Fedora, provided your program is already portable.

Footnotes:

  1. People who stick to the Free Software ideology can still be supportive of the idea of making their libre programs available on proprietary platforms. One goal is to let the users of such platforms have at least partial freedom, until they can make a full switch.
  2. The last win32 in x86_64-w64-mingw32-gcc-win32 apparently refers to the Win32 API; there is a file without that suffix which does the same job, and no, there is no file with win64 suffix.
  3. You might want to use the -municode flag to deal with the error undefined reference to `WinMain', if the example you downloaded has wWinMain as the entry point.
  4. DLLs for harfbuzz were missing, which could be downloaded from https://github.com/harfbuzz/, but their extensions had to be changed from .dll to .dll.a for gcc to find them (other than adding the path using -L).
  5. WINEPATH has to be set to the bin/ directory of the GTK bundle.

UPDATE on 2023-06-20: This was just me experimenting with various ways to build GTK apps for Windows. If you are looking for a definitive guide that uses the same MinGW-based method, there is this excellent post that deals with everything including setting an icon, picking a Windows-friendly theme, creating an installer and even automating the build process using a Fedora container (I have not tried any, though): https://wrycode.com/gtk3-cross-compile/

Read more from Nandakumar at nandakumar.org/blog/