Logo Computer scientist,
engineer, and educator
• Articles • Articles about computing • Articles about software development

Building native graphical applications in C for MS Windows, using GTK+ and open-source tools

GTK+ logo This article describes how to use free, open-source tools to develop graphical applications in C for Microsoft Windows, which run natively. By 'natively' I mean that they don't need any supporting infrastructure (as Cygwin does), and can be packaged and distributed just as any other Windows application, and will look and behave like one.

I will be using the GTK+ graphical user interface (GUI) libraries to provide a Windows-like user interface. GTK+ is widely used — almost ubiquitous, in fact — in Linux development, but has only recently started to get a substantial foothold in the Windows world. A bonus of using GTK+ is that — with care — applications will be portable between Windows and Linux. For many people this is a very big deal. Traditionally, to create a graphical application that ran on both Windows and Linux we had to use a programming language, like Java, that was specifically designed for that kind of compatibility. But you can't create a native application for Windows using Java — it requires a complete runtime system which has complicated redistribution requirements, both legal and technical. Everything described in this article is open-source and can be freely repackged and distributed.

In this article, I use only standard Windows utilities (like Notepad) and command-line tools. I'm not suggesting for a moment that you'd want to build a real application this way, but it does help to demonstrate exactly what's going on when you compile and link code under Windows. The application itself will be the simplest 'Hello, world' example I could come up with using GTK+.

Please note that this article is written for experienced C developers. In particular it assumes that you are able to make sense of compiler error messages, and are willing to use the GNU gcc compiler from a command line. I also assume that you have some knowledge of GTK+, or at least are willing to learn. There are plenty of GTK+ tutorials, but they all assume that you have a working build environment, which is where this article comes in. I'm tending to assume that people who are likely to read this article are Linux developers, looking to port their applications to Windows, or to start developing for Windows. Consequently I will frequently refer to the differences between Linux and Windows in this respect.

Note:
It's very likely that, by the time you read this, version numbers of software components will have changed. Because I'm refering to DLLs and similar files explicitly by filename in this article, you should be prepared to have to adjust filenames to accomodate version changes. There are ways to avoid this problem in a pratical, automated build environment, but it isn't the purpose of this article to explain how to do so.
When time allows, a later article will explain how to set up a practical build environment, which allows both Windows and Linux applications to be build from exactly (well, almost exactly) the same sources.

Getting and installing the tools

To follow the example in this article you will need to download and set up the following software:
  • MinGW (Minimal GNU for Windows). This is, essentially a native Windows port of the GCC C compiler toolchain and standard library, and some Windows extensions
  • GTK+ build for MinGW
These days the recommended way to install MinGW is to use the automated installer (download page). The automated installer will strongly recommend installing in the directory c:\MinGW. While this does not conform to Windows conventions for installing software, I would strongly recommend using this location — it's really more trouble than it's worth to change it, and all the documentation and tutorials you're like to see assume this directory.
Note:
The automated installer will probably offer to install MSYS as well. MSYS is a set of utilities like GNU make. You won't need any of these for this example, but they are exceptionally useful for real build projects.

Rather awkwardly, the GTK+ build for MinGW is maintained as a bunch of separate packages (presumably because this is how it's done for Linux). All these packages are available from the download page, in the form of archives. You'll need to download and unpack (into c:\MinGW) almost all the 'dev' and 'runtime' packages on that page — you might as well get everything. You won't need the pkg-config utility for this example, but it is important in any automated build system. The 'dev' components are needed for building applications, but won't need to be distributed with it. The 'runtime' components will need to be distributed because, unlike Linux, Windows users won't normally have a system-wide GTK+ runtime available. A mechanism for bundling and distributing the runtime components will be discussed later.

Setting up the command prompt

In this article, all the building will be done from the standard Windows command prompt (cmd.exe). The Cygwin bash prompt is much nicer to work with, particularly if you're familiar with it from Linux, but the MinGW tools are not built for Cygwin — They are native Windows applications. Consequently, if you do use the Cygwin prompt — or any build system intended for Cygwin — you're going to have to be quite careful about things like path separators.

The only thing it should be necessary to do to set up the command prompt is to extend the PATH so the command line can find the C compiler and libraries. It's also handy to create a directory to keep the application code in — I'm using f:\source\hello_world in this example.

C:\>F:

F:\>cd source\hello_world

F:\source\hello_world>PATH=%PATH%;c:\MinGW\bin

F:\source\hello_world>gcc
gcc: no input files
If you can run gcc at the prompt and get the message 'no input files' then most likely you're all set.

Compiling a simple console-mode program

Before launching into the complexities of GTK, we will build a simple console-mode application, just to check that the C compiler is working correctly and all the C runtime bits are available. Here's the example I will use, saved as a file hello.c:
#include <stdio.h>;

int main ()
{
  printf ("hello world\n");
}

Compile and run the example from the prompt as follows:
F:\source\hello_world>gcc -o hello hello.c

F:\source\hello_world>hello
hello world
-o hello tells gcc to write the output as the file hello.exe — the default is the rather unmemorable a.exe.

Note that this example does not create any graphical interface, or need one. It only produces console output. You might wonder what would happen if we ran hello.exe in a context other than a command prompt — for example, by double-clicking it in Windows Explorer. We'll come back to this issue later, because it's surprisingly vexing for those of us who are more familiar with the Linux way of doing things.

Compiling a simple graphical program

This section deals without compiling, but not linking, a graphical GTK+ application. I'm distinguishing compiling from linking to demonstrate better the technicalities, but also because in a project of any scale you'd almost certainly want to do this. In addition, for Windows development there are things we'll need to link that are not executable code (specifically, Windows resource objects — more later) so it's important to understand the separate steps.

Here is hello.c again, extended to create a GTK+ dialog box which will show the message:

#include <stdio.h>;
#include <gtk/gtk.h>;

int main (int argc, char **argv)
{
  printf ("hello world\n");


  gtk_init (&argc, &argv);


  GtkDialog *dialog = GTK_DIALOG (gtk_message_dialog_new
    (NULL,
     GTK_DIALOG_DESTROY_WITH_PARENT,
     GTK_MESSAGE_ERROR,
     GTK_BUTTONS_CLOSE,
     "Hello, world"));

  gtk_dialog_run (dialog);

  gtk_widget_destroy (GTK_WIDGET (dialog));
}
Note that conventionally the GTK+ main header file gtk.h is referenced:
#include <gtk/gtk.h>;
and not
#include <gtk.h>;
Since we're going to have to tell gcc exactly where this header is (and a whole bunch of others), in principle we could use the shorter formulation in the code and adjust the directory supplied to gcc. In the longer term, this would be a bad idea, because many automated build tools will assume that you've followed the convention, for gtk.h and all the other headers you're likely to need in a full-scale project.

If you try to compile this application as the console example above, it won't work, because of the missing headers. Irritatingly, you'll need to tell gcc not only where gtk.h is, but all the other headers that gtk.h references. So to compile this example you're going to need the following command (sorry):

F:\source\hello_world>gcc
  -I c:\MinGW\include\gtk-2.0\
  -I c:\MinGW\include\glib- 2.0\
  -I c:\MinGW\lib\glib-2.0\include
  -I c:\MinGW\lib\gtk-2.0\include
  -I c:\MinG W\include\cairo
  -I c:\MinGW\include\pango-1.0
  -I c:\MinGW\include\
  -I c:\MinGW\i nclude\atk-1.0
  -mms-bitfields
  -o hello.o -c hello.c
Of course, this should all be on one long line. We're getting to a point where simple use of the command prompt is going to be problematic, and we'd do better to put this command into a batch file (or Makefile, or whatever).

GLib (utility library), Pango (text renderer), Cairo (2D graphics library), and ATK (accessibility interface) are all software components on which GTK has depenencies, and which will need to be bundled with the final application — more on this later. Note that they all have different versions, and the version numbers might have changed by the time you read this.

If successful, the gcc command above will produce the object file hello.o, ready for linking with the GTK libraries to produce an application. The -c switch tells gcc not to link, because linking will fail at this stage.

The -mms-bitfields merits further explanation: this tells gcc to pack C data structures into memory in a way that is compatible with Microsoft compilers. This is a big deal if your C application will call Windows APIs directly (rather than, for example, using the GTK+ wrappers). Even if your application won't call Windows APIs, it's still a big deal, because the GTK+ libraries will have been compiled with this switch. C code compiled with --mms-bitfields is simply not compatible with code compiled without it, even from the same C compiler.

Linking the simple graphical program

Linking presents the same sorts of problems as compiling: you need to tell gcc where the libraries are. You also need to tell it what libraries to link — it won't attempt to figure this out. Unlike problems with not being able to find missing header files — which you can probably fix by trial and error — it's hard to figure out what libraries are lacking unless you're already familiar with GTK+ programming. In any event, here's the command you need:
F:\source\hello_world>gcc
  -L c:\MinGW\bin
  -o hello hello.o
  -lgtk-win32-2.0 -lglib-2.0 -lgobject-2.0
Again, you'll need to put this on one long line, or in a batch file.

In this command, the -L switch is specifying where the libraries are, and the -l switches what libraries must be linked. GObject, which we haven't encountered before, contains the implementation of the GTK object-oriented model.

There's a complication here that will bite us later — we've specified the libraries that are required at link time, but that isn't by any means all the libraries that will be required at run time. GTK will attempt to load a whole bunch of other stuff dynamically, and it needs to know where to find it — again, it's not something it can guess. More on this point later.

Running the graphical program

If the link operation was successful it will have produced an executable hello.exe as before, and you should be able to run it from the prompt. All being well, you should see the application's window:

Screenshot

OK, so it's not the most impressive application in the world, but it is a genuine, native windows GUI program. From the screenshot you'll note that the application also produces console output, as before.

One thing that gives away the GTK+ origin of this application is the generic GTK+ logo icon in the window's caption bar. This is the icon you'll see if you create a shortcut to the application on the desktop as well, and you'll almost certainly want to replace it with an application-specific one — details later.

Bundling the graphical program for distribution

Assuming you've got the application running from the command prompt, I suggest trying the following experiment, which demonstrates why distributing GTK+ applications can be somewhat problematic.

Start a new command prompt, cd to the directory containing hello.exe and try to run it. I suspect you'll get a result something like this:

Screenshot

Why should it work from one command prompt and not the other? You'll get the same (non-)result if you try to run hello.exe from the Explorer. If you're a Windows developer you'll probably find the answer more readily than a Linux developer, because it turns on the way dynamic library loading works in Windows. When we linked hello.exe we had to tell gcc where to find libgobject-2.0-0.dll (and all the other DLLs) so it could do the link. But gcc does not store this information in the executable, because if it did it would make the application depend on the absolute directory where the GTK+ components were installed. So we need to tell Windows at run time where these bits are. The reason this worked from the previous command prompt is because we had already set PATH to include the MinGW bin, which is where all the DLLs are. Linux does not work this way — the library loader will not automatically look for libraries in PATH. To create an application you can distribute, you'll need to bundle the GTK+ libraries, and find a way to tell Windows where to find them when running the application.

The problem of bundling GTK+ components rarely arises in the Linux world, because there will typically be a single, system-wide installation of GTK+, and the library loader will know where to find it. On Windows, however, where GTK+ is still relatively new, you'll have to give the operating system a bit of help.

There are essentially three ways to bundle GTK+ libraries with an application for distrubution.

  • Link all the GTK+ bits with the application into one huge, fat lump (ugh!)
  • Create an installer that will put the GTK+ libraries into a system location
  • Create an installer that will put the libraries into a new directory along with the application itself
Of these, the first is just too ugly to consider. The second has certain merits, but the installer ought to check that there is not already an installation of GTK+ in whatever location you choose. If there is already an installation, and it is different from the version you need, then you might run into compatibility problems.

In practice, so far as I know all GTK+ developers working on Windows adopt the third approach, which is to bundle the application and all the libraries into a common directory. This works because Windows will always look for libraries in the same directory as the executable that invokes them — you don't have to tell it to do this, any more than you have to tell it to look in PATH. Again, this is rather different from the Linux approach, but whether it's better or worse I'm not sure. Anyway, it's easy to implement and test.

To build a bundle for distribution, my approach is to have my build system create a directory and copy all the necessary DLLs from the GTK+ distribution, along with the application itself. There's really no obvious way to work out the complete set of libraries an application needs — bundling is a matter of observing the error messages as in the screenshot above, and copying the missing DLL into the bundle directory. If you do this, you should find that, not only can you run the application from any prompt, you can also run it from the Explorer, or create a desktop shortcut to it. The screenshot below shows hello.exe being run from a directory containing the necessary DLLs — it also shows the rather extensive list of DLLs that even this trivial application needs. However, this list won't get much longer even with a full-scale application.

With care, the directory you build up here contains everything you need for a deployable application, and all you need to do to keep users happy is to bundle up an installer that will dump all this stuff into a directory under c:\program files. Most of the widely-used Windows installer builders can do this in a couple of mouse clicks.

Screenshot

Console-mode and GUI-mode issues

If you run hello.exe from the Explorer as shown above, you'll probably be disagreeably surprised to find that you also get a console prompt. With a Windows GUI application, you almost certainly don't want this.

Screenshot

The reason this happens is that you've built a 'console mode' application, and so Windows gives you a console, whether you want one or not. It wouldn't make any difference if the application produced no console output — you'd just get an empty black box instead. Worse, if you close the console window, it kills the application as well.

The fact that Windows recognizes distinct console-mode and GUI-mode applications is a source of vexation for Linux programmers, because Linux behaves much more sensibly in this respect. Linux has no separate console mode: any application can produce console output, and if you don't have a console you can either arrange for that output to be captured some other way, or just let it silently disappear. But Windows, irritatingly, forces you to make a choice.

Telling gcc to create a GUI-mode application is as simple as adding the switch -mwindows to the command line at link time. This will get rid of the irritating console box. But if you do this, you won't be able to get console output even if you run the application from a console or a debug framework. For basic application debugging during development, this is a real pain.

If you're using an automated build process, the simple solution is to have it build two completely separate binaries — one with the -mwindows switch and one without. Then you can use the console-mode version for testing and the GUI version for distribution. In practice, in a large scale project you'll probably want to build slightly different 'debug' and 'production' releases anyway, so this is no great hardship.

Resources and icons

It's unusual for Linux executables to contain any binary elements except executable code and any associated data. For better or worse, this is not the case for Windows GUI applications, which conventionally embed user interface specifications in the executable. Traditionally these elements include icons, stringlists (for internationalization purposes), menus, and user interface widget layouts. In the Linux world, if these things are used they are typically stored in separate files.

In practice, if you're using GTK+ you will probably want to build user interfaces the GTK+ way, rather than using native Windows API methods. The only interface element that isn't easy to replicate using GTK+ is the application icon, and that is because it's read by the Windows desktop and not by the application itself. I don't know of any way to embed an icon into an executable other than by treating it as a Windows resource file element, and compiling it using the windres resource compiler which is part of MinGW. This is a bit of a kerfuffle just to install an icon but, of course, it can be automated.

To install an icon we'll need to create a minimal resource file — it's just a one line text file so we can use Notepad or any other editor. Here is what the file should contain:

1 ICON "icons/app.ico"
I have called this file app.rc. The icons themselves can be in any directory and have any name. You can specify multiple icons in one .rc file if you wish, or create a separate file for each. You'll also need a way to create icons in the appropriate format — they're not something straightforward like PNG images. Happily, a number of free and/or open-source icons editors are available.

Once you've created your icon and installed it in the location specified in the .rc, the procedure is to run winres to convert it to an object (.o) file. This file can then be incorporated into the executable at link time.

F:\source\hello_world>windres app.rc -o app.o

F:\source\hello_world>gcc -L c:\MinGW\bin
   -o hello
   hello.o app.o -lgtk-win32-2.0
   -lglib-2.0 -lgobject-2.0 -mwindows
For a Linux developer, it seems odd to be putting things into the executable that the application does not need. In principle, the application can locate resource (like icons) created using windres, using native Windows API calls. However, if it's just a case of adding a program icon, then that should not be necessary.

Closing remarks

The procedure described in this article probably looks complicated, particular compared to using Microsoft Visual Studio and just hitting the 'Run' button. However, if you're familar with tools like Make, or even Windows batch files, then all the complexity can be automated, and building the application becomes a one-step operation.

All the same, if you're targeting the Windows platform specifically, and no other, then using open-source tools as described here doesn't really offer any advantage over MS Visual Studio, and has the disadvantage of needing to learn how to use a whole new set of tools and APIs. Where the open-source approach really wins is in supporting Windows and Linux builds using the same code. With a little thought, you can set up a build system that will create both Windows and Linux installable packages using essentially the same code and configuration files, and this could represent an enormous productivity bonus in the longer term.

Copyright © 1994-2013 Kevin Boone. Updated Aug 03 2013