Dumbbell's home

Cross-compilation vers un environnement Win32

Vous savez surement à quel point il est désagréable de ne pas pouvoir retrouver vos logiciels, vos habitudes en passant d'un système à un autre. Vous trouvez irritant le fait de devoir rebooter pour consulter vos mails, pour ouvrir un document, etc. Vous voulez donc que vos propres applications puissent fonctionner correctement, peu importe le système de l'utilisateur, sans pour autant être contraint de vous-même redémarrer ou utiliser une autre machine pour compiler votre programme.

Si vous avez l'habitude de coder sous Unix/Linux, mettre en place un environnement qui va vous permettre de compiler à la fois pour lui et pour d'autres plateformes devrait vous faciliter les choses. Le principe est toujours le même, et ici, nous allons discuter de l'installation d'un cross-compilateur sous Linux, produisant des binaires pour Microsoft Windows.

Si votre OS possède un système de packages ou de ports, vous pouvez l'utiliser et passer directement aux essais (Les essais). Si au contraire, votre OS n'en a pas ou si vous préférez l'installer par vous même, vous aurez besoin des logiciels suivants :

Nom de la cible

De la même manière que votre système a un identifiant pour désigner le type de plateforme, il nous en faut un pour désigner la plateforme cible. Prenons quelques exemples :

Comme vous le voyez, ce nom se divise en 2 parties : la machine, puis l'OS. Nous allons donc construire un nom sur ce même modèle :

Au final, sur l'Athlon XP de tout à l'heure, le nom complet est :

i686-pc-mingw32

MinGW

Le prompt

Pendant le processus, la majeure partie des étapes est faite en tant qu'utilisateur non privilégié, mais toutes les installations peuvent demander un accès administrateur (sauf si vous décidez d'installer l'environnement dans votre home par exemple). Par convention, les commandes ne necessitant pas un accès privilégié, auront le prompt suivant :

$ commande

Tantdis que les commandes pouvant requérir un accès root auront un prompt comme celui-ci :

# commande

La première chose à faire est d'installer les librairies et les headers que binutils, GCC et toutes nos cross-compilations vont réclamer (l'équivalent de la glibc sous Linux). C'est également maintenant que vous décidez où vous voulez installer tout l'environnement. Dans l'exemple, nous allons prendre /usr/local/cross. On commence par créer le répertoire d'installation en tant que root, dans lequel on va décompresser les 2 archives :

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

Nous avons créé un répertoire portant le nom de la plateforme cible. C'est à l'intérieur que les archives vont :

i686-pc-mingw32# tar zxf /chemin/vers/mingw-runtime-3.3.tar.gz
i686-pc-mingw32# tar zxf /chemin/vers/w32api-2.5.tar.gz

Comme tar garde le nom du propriétaire, tous les fichiers fraichement décompressés peuvent appartenir à n'importe qui sur votre machine. Il est donc plus prudent de les rendre à l'utilisateur root :

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

Le premier package, mingw-runtime, correspond entre autres à tous fichiers que le compilateur et le linker auront besoin pour produire des binaires pour Windows. Le second contient, comme son nom l'indique, toute l'API Win32, c'est-à-dire les headers et les librairies de base.

Maintenant, il va s'agir de compiler les binutils, puis GCC. Pour cela vous n'avez plus besoin d'être root.

Les binutils

Comme pour la plupart des compilations, tout se déroule en 3 étapes :

Une fois que vous avez décompressé l'archive des binutils quelque part, vous devez créer un autre répertoire à côté, dans lequel se déroulera la compilation, parce qu'il est recommandé de ne pas travailler directement dans le répertoire décompressé :

build$ tar jxf /chemin/vers/binutils-2.14.tar.bz2
build$ mkdir binutils-build
build$ cd binutils-build

Maintenant, nous allons lancer le configure script, avec 2 paramètres : l'un pour donner le prefix (c'est-à-dire le répertoire que nous avons créé au tout début, ici, /usr/local/cross), et le nom de la cible (sinon, les binutils vont se compiler pour la plateforme hôte). Le configure devient :

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

Ensuite, c'est l'heure de la compilation, qui va assez vite (par rapport à GCC) :

build/binutils-build$ make

Une fois terminée avec succès, il nous reste à installer tout ceci, en tant que root :

build/binutils-build# make install

C'est maintenant terminé pour les binutils. Il vous reste à mettre à jour votre $PATH si le chemin d'installation que vous avez choisi n'y est pas (ceci fonctionne avec Bash, consultez la documentation de votre shell s'il est différent) :

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

Pour tester que tout fonctionne, vous pouvez faire ceci :

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

Les binutils ont donc été compilé pour produire un format binaire i386pe, le PE étant bien le format supporté par Microsoft Windows.

GCC

Pour GCC, nous allons refaire exactement la même chose qu'avec les binutils. Commençons avec la décompression et la création du répertoire de travail :

build$ tar jxf /chemin/vers/gcc-core-3.4.0.tar.bz2
build$ tar jxf /chemin/vers/gcc-g++-3.4.0.tar.bz2
build$ mkdir gcc-build
build$ cd gcc-build

Ici, nous avont décompressé les archives du compilateur C et C++.

Ensuite, la ligne du configure est identique à celui des binutils :

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

Cette fois-ci, il faut bien vérifier que le configure trouve les binutils installés précédemment. Il doit afficher quelque chose comme :

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

Si au lieu de ça, il affiche des lignes comme la suivante, la compilation ne fonctionnera pas, il faut revérifier votre $PATH :

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

Quand tout est bon pour le configure, nous passons à la compilation, qui cette fois, sera plus longue :

build/gcc-build$ make

Quand la compilation est terminée, il serait bon de tester le compilateur avec make check (et gcc-testsuite), mais tous les tests d'execution vont évidemment échouer.

Nous allons conclure cette partie GCC avec l'installtion en tant que root :

build/gcc-build# make install

Comme vous êtes passé root, il se peut que le $PATH ne soit plus à jour, et l'installation échouera peut-être. Dans ce cas, répétez la mise à jour de l'environnement, comme nous l'avons fait à la fin de l'installation des binutils. Après cette étape, vous pouvez revenir à votre compte normal (non privilégié).

Comme contrôle basique, nous pouvons demander à GCC pour quelle plateforme il est prévu :

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

Les essais

Maintenant, vous mourrez d'envie d'essayer tout ça, donc débutons avec le programme ultime pour ce moment unique, le Hello World :

#include <stdio.h>

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

Vous connaissez déjà par cœur ce code source, passons directement à la compilation :

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

Comme vous le voyez, GCC a produit hello.exe. Pour le lancer, vous pouvez utiliser Wine (ou WineX) ou une machine sous Windows, évidemment. Voici l'émouvant résultat avec Wine :

build/hello$ wine hello.exe
Hello World !

Essayons un exemple graphique maintenant. L'objectif est d'afficher une simple boite de dialogue, ce qui nous force à utiliser le header windows.h et le point d'entré WinMain spécifique aux applications Win32 :

#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;
}

Ensuite viens la compilation, identique à la précédente :

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

Une fois compilé, nous pouvons le tester comme précédemment avec Wine :

build/hello$ wine hello_ui.exe

Admirons ensemble le précieux Saint Graal :

Capture d'écran de hello_ui.exe

Utilisation avec un script configure

A présent, notre nouveau compilateur est prêt à entrer en production. Pour compiler un simple fichier, nous avons vu ci-dessus comment faire mais pour un logiciel entier, cela peut-être plus délicat. Heureusement quand l'application utilise autoconf/automake, tout redevient simple pour nous. Il nous suffit de faire la même chose qu'avec les binutils ou GCC :

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

Il ne reste plus qu'à installer le résultat sur la machine de destination et à tester.

Utilisation dans vos projets

Pour l'appliquer à vos projets, il n'y a rien de plus à faire. Mais comme pour Linux, la compilation pour Windows a également besoin de headers et librairies qui ne sont pas toujours dans la bibliothèque standard. Il vous faudra donc récupérer des binaires (ou les compiler vous-même) Win32 des librairies dont votre logiciel dépend, et ajouter les options nécessaires aux CFLAGS, CXXFLAGS, CPPFLAGS, LDFLAGS, etc. (par exemple, -logg pour lier à libogg).

A la différence des librairies .so de Linux, Windows utilise des DLL (.dll). Et quand il faut lier le binaire, il y a un second fichier indispensable, le .lib. Vous devez donc avoir ces 2 fichiers pour lier puis executer votre application.