Dumbbell's home

Cross-compiling for a Win32 environment

You must know how frustrating it is when you can't use your favorite softwares under a new operating system. Always have to reboot isn't an acceptable workaround for day to day use (read your mails, open a document, etc.). Now, you don't want your programs suffer the same limitation: you need them to run under many OS, without the need to restart your computer or use another one to compile.

If you used to work under Unix/Linux, set up an environment to build natively for your host system and for other targets should ease your job. The procedure is always the same, and here, we'll discuss a cross-compiler to build for Microsoft Windows, under Linux.

If your OS has a package management system, you should use it first. You can then continue with the tests (Testing the cross-compiler). If the package manager doesn't provide something, or you prefer to do it be yourself, you'll need the following components:

Target name

The same way your system use an id to qualify the platform, we need one to designate the target platform. Take the example below:

As you can see, the name is divided into 2 parts: the architecture and the OS. Thus we prepare an id on these observations:

At the end, here is what we have for the previous Athlon XP:

i686-pc-mingw32

MinGW

The prompt

During the process, most of the commands can run under an unprivileged user account. Despite this, installations may require a root account (for system-wide installs). By convention, commands which doesn't need an administrative account will use the following prompt:

$ command

Commands which require a root access will look like:

# commande

The first thing to do is to install the libraries and headers needed by binutils, GCC and all cross-compilations. It is now that you decide where you install the build tools. We'll take /usr/local/cross in this example. We begin with the creation (as root) of this directory, where we can untar both the packages:

# mkdir /usr/local/cross; cd /usr/local/cross
cross# mkdir i686-pc-mingw32; cd i686-pc-mingw32

We made a directory named with the platform id. The packages go in it:

i686-pc-mingw32# tar zxf /path/to/mingw-runtime-3.3.tar.gz
i686-pc-mingw32# tar zxf /path/to/w32api-2.5.tar.gz

As tar keeps the owner of every files, they may be owned by someone else. It is safe to change it back to root:

i686-pc-mingw32# chown -R root:root .
i686-pc-mingw32# chown -R 664 *
i686-pc-mingw32# chown -R +X *

The first archive, mingw-runtime, contains files required by the compiler and the linker to produce Windows binaries. The second brings the Win32 API, ie. standard headers and libraries.

Now is a good time to compile the binutils, than GCC. For these steps, you don't need to be root anymore.

Binutils

As usual, the building process can be divided into 3 parts:

Once you untar'd the binutils' archive, you must create a directory at the same level, where the build will take place. It's recommanded to not work in the just uncompressed directory:

build$ tar jxf /path/to/binutils-2.14.tar.bz2
build$ mkdir binutils-build
build$ cd binutils-build

We're ready to run the configure script. It'll take 2 arguments: one for the installation prefix (the directory we've created earlier, here /usr/local/cross), one for the target (or the binutils would build a version for the current host):

build/binutils-build$ ../binutils-2.14/configure \
  --prefix=/usr/local/cross \
  --target=i686-pc-mingw32

Compile time (not very long, compared to GCC):

build/binutils-build$ make

Once successfully done, the following command will install everything (requires a root access):

build/binutils-build# make install

Ok, finished for the binutils. You have to update your PATH, if the install path isn't already in it (this command will work for Bash; consult the document of your shell if necessary):

build$ export PATH="/usr/local/cross/bin:$PATH"

To test the binutils, you can issue the command:

build$ i686-pc-mingw32-ld -V
GNU ld version 2.14 20030612
  Supported emulations:
   i386pe

Binutils have been built to produce a i386pe format and PE is the one suported by Microsoft Windows.

GCC

For GCC, we are going to do the same thing. Let's begin with the package uncompression, and the working directory:

build$ tar jxf /path/to/gcc-core-3.4.0.tar.bz2
build$ tar jxf /path/to/gcc-g++-3.4.0.tar.bz2
build$ mkdir gcc-build
build$ cd gcc-build

Here, we're interested in the C and C++ compilers.

Next, the configure command line is the same as for the binutils:

build/gcc-build$ ../gcc-3.4.0/configure \
  --prefix=/usr/local/cross \
  --target=i686-pc-mingw32

This time, you must check the configure output: it should find the binutils' tools. Here's an example:

checking for i686-pc-mingw32-ar... i686-pc-mingw32-ar
checking for i686-pc-mingw32-as... i686-pc-mingw32-as
checking for i686-pc-mingw32-dlltool... i686-pc-mingw32-dlltool
checking for i686-pc-mingw32-ld... i686-pc-mingw32-ld
checking for i686-pc-mingw32-nm... i686-pc-mingw32-nm
checking for i686-pc-mingw32-ranlib... i686-pc-mingw32-ranlib
checking for i686-pc-mingw32-windres... i686-pc-mingw32-windres

If you don't obtain this, but the following output, the build won't be ok. Check your PATH:

checking for i686-pc-mingw32-ar... no

When everything is fine, we start the longer build process:

build/gcc-build$ make

At the end, you can test the compiler, but every run tests will fail obviously. So if you have uncompressed gcc-testsuite too, you can run make check.

We'll conclude this part with the installation of GCC, as root:

build/gcc-build# make install

Now that you're root, the PATH may be wrong and the make install may fail. To fix this, update your PATH. After this, you can go back to your normal user account (unprivileged).

A basic test consists of asking GCC the platform it was built for:

build$ i686-pc-mingw32-gcc -dumpmachine
i686-pc-mingw32

Testing the cross-compiler

Now, i guess you can't wait to test all these, so let's start with the famous Hello World:

#include <stdio.h>

int main()
{
  printf("Hello World !\n");
  return 0;
}

You already known this source code, we can focus on the compilation:

build/hello$ i686-pc-mingw32-gcc -o hello.exe hello.c
build/hello$ ls
hello.c  hello.exe

As you can see, GCC produced hello.exe. To run it, you may choose between Wine (or WineX) or a computer running Microsoft Windows. Here is the brilliant result:

build/hello$ wine hello.exe
Hello World !

Next, we want to try a GUI example. The goal is to display a simple dialog box, to force us to use the windows.h header, and the Win32 specific entry point, WinMain:

#include <windows.h>

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
  MessageBox(NULL,
    "Cette fenêtre prouve que le cross-compilateur est fonctionnel !",
    "Hello World", MB_OK);
  return 0;
}

Time to compile it, as above:

build/hello$ i686-pc-mingw32-gcc -o hello_ui.exe hello_ui.c
build/hello$ ls
hello_ui.c  hello_ui.exe

And run it:

build/hello$ wine hello_ui.exe

Isn't this screenshot amazing ?

hello_ui.exe screenshot

Cross-compiling with a configure script

Now, the cross-compiler is fully operationnal. We've already seen how to do for a single file, but for a whole software, it may be more complicated. Hopefully, when a package uses autoconf/automake, things area still easy. As for the binutils or GCC, you must change the target:

build/monlogiciel$ ./configure --target=i686-pc-mingw32
build/monlogiciel$ make

Next, you must install the result on the target and test it.

For your own projects

To apply this to your own projects, there's nothing more to do. But the same way, Linux binaries must be linked to libraries sometimes, Windows binaries must linked to DLL. The syntax is the same (we are still using GCC and the binutils), and the variables to set this are the usual CFLAGS, CXXFLAGS, CPPFLAGS, LDFLAGS, etc. Therefore to link your program with libogg, just add -logg to LDFLAGS. Obviously, you need a Win32 binary of libogg.

Unlike Linux' .so, Windows uses DLL (.dll). But when you are about to link your program, a second file, .lib is required.