CPP/CrossCompilation

De TartareFR
Aller à la navigation Aller à la recherche

Pour générer un exécutable windows depuis linux, il faut recourir à ce qu’on appelle la cross-compilation: compiler sur un système pour un autre système. On va partir d’un simple “Hello world”. A ce propos, j’ai découvert une collection d’hello world sur Github.

Compilation d'un fichier unique

Sources

Le fichier source en C

TextFileIcon16.png main.c
#include<stdio.h>
int main()
{
  printf("Hello World !\n");
  return 0;
}

Le fichier source en C++

TextFileIcon16.png main.cpp
#include <iostream>
using namespace std;
int main()
{
  cout << "Hello World !" << endl;
  return 0;
}

Compilation manuelle

GNU/Linux

Pour GNU/Linux, le fichier C serait compilé avec la commande

gcc -O0 -Wall -g -o helloc main.c

et le fichier C++

g++ -O0 -Wall -g -o hellocpp main.cpp

Wind@uze

On va se servir des compilateurs mingw (32 et/ou 64 bits)

Version 32 bits

On installe les compilateurs C et C++ <class>mingw</class> 32 bits ainsi que la collection d'utilitaires

dnf install mingw32-gcc mingw32-gcc-c++ mingw32-binutils

le fichier C sera compilé avec la commande

i686-w64-mingw32-gcc -O0 -Wall -g -o helloc32.exe main.c

et le fichier C++

i686-w64-mingw32-g++ -O0 -Wall -g -o hellocpp32.exe main.cpp

En testant le binaire généré à partir du C++ avec <app>wine</app>, on se rend compte qu’il ne fonctionne pas (absence de DLLs).

err:module:import_dll Library libstdc++-6.dll (which is needed by L"V:\\hello32.exe") not found

Pour résoudre ce problème, il va falloir copié quelques DLLs dans le même répertoire que l’exécutable

cp /usr/i686-w64-mingw32/sys-root/mingw/bin/libstdc++-6.dll .
cp /usr/i686-w64-mingw32/sys-root/mingw/bin/libgcc_s_sjlj-1.dll .
cp /usr/i686-w64-mingw32/sys-root/mingw/bin/libwinpthread-1.dll .
Idea.png
Test des exécutables avec wine
Wine affiche énormément de trace de debug (FIXME) dans la console et le message provenant de nos exécutables risque d'être noyé au milieu des traces de wine. Il est préférable de désactiver certaines traces:
export WINEDEBUG=fixme-all

On peut maintenant tester un exécutable

wine win32/cpp-helloworld.exe
Version 64 bits

On installe les compilateurs C et C++ <class>mingw</class> 64 bits ainsi que la collection d'utilitaires

dnf install mingw64-gcc mingw64-gcc-c++ mingw64-binutils

le fichier C sera compilé avec la commande

x86_64-w64-mingw32-gcc -O0 -Wall -g -o helloc64.exe main.c

et le fichier C++

x86_64-w64-mingw32-g++ -O0 -Wall -g -o hellocpp64.exe main.cpp

Pour résoudre le même problème que la version 32 bits, il va falloir copié quelques DLLs dans le même répertoire que l’exécutable.

cp /usr/x86_64-w64-mingw32/sys-root/mingw/bin/libstdc++-6.dll .
cp /usr/x86_64-w64-mingw32/sys-root/mingw/bin/libgcc_s_seh-1.dll .
cp /usr/x86_64-w64-mingw32/sys-root/mingw/bin/libwinpthread-1.dll .

Compilation avec Makefile

Chaque architecture aura son propre répertoire d'accueil afin d'éviter le chevauchement de DLLs (même nom mais architecture différente) et fabriquera deux binaires: un avec un fichier source en C et l'autre avec un fichier source en C++.

On va définir plusieurs cibles principales:

  • linux: ce sera la cible par défaut et elle construira les binaires ELF dans le répertoire <path>linux</path>
  • win32: elle construira les binaires Windows 32 bits dans le répertoire <path>win32</path>
  • win64: elle construira les binaires Windows 64 bits dans le répertoire <path>win64</path>
  • win: elle lancera les cibles win32 et win64
  • all: elle lancera les cibles linux, win32 et win64
  • clean: elle nettoiera le projet

D'autres cibles secondaires seront définies:

  • tree: Créera si besoin le répertoire d'accueil des binaires
  • copy-dll32: copiera si besoin les DLLs 32 bits dans le répertoire d'accueil des binaires Windows 32 bits
  • copy-dll64: copiera si besoin les DLLs 64 bits dans le répertoire d'accueil des binaires Windows 64 bits
Idea.png
@ dans les commandes
Certaines commandes du fichier <path>Makefile</path> sont précédées d'un @ afin de ne pas écrire la commande en cours sur la sortie standard.
TextFileIcon16.png Makefile
CFLAGS=-O0 -Wall -g
CXXFLAGS=-O0 -Wall -g
LDFLAGS=
WINFLAGS=
CTARGET=c-helloworld
CXXTARGET=cpp-helloworld
 
DLL32_PATH := /usr/i686-w64-mingw32/sys-root/mingw/bin
DLL64_PATH := /usr/x86_64-w64-mingw32/sys-root/mingw/bin
 
linux: tree c-linux cpp-linux

win: win32 win64

win32: tree c-win32.exe cpp-win32.exe copy-dll32

win64: tree c-win64.exe cpp-win64.exe copy-dll64

all: linux win

tree:
	@[ -d linux ] || mkdir linux
	@[ -d win32 ] || mkdir win32
	@[ -d win64 ] || mkdir win64

cpp-linux: 
	@g++ $(CXXFLAGS) -o linux/$(CXXTARGET) main.cpp

c-linux: 
	@gcc $(CFLAGS) -o linux/$(CTARGET) main.c

c-win32.exe:
	@i686-w64-mingw32-gcc $(CFLAGS) -o win32/$(CTARGET).exe main.c

cpp-win32.exe:
	@i686-w64-mingw32-g++ $(CXXFLAGS) -o win32/$(CXXTARGET).exe main.cpp

c-win64.exe:
	@x86_64-w64-mingw32-gcc $(CFLAGS) -o win64/$(CTARGET).exe main.c

cpp-win64.exe:
	@x86_64-w64-mingw32-g++ $(CXXFLAGS) -o win64/$(CXXTARGET).exe main.cpp

copy-dll32:
	@[ -f libwinpthread-1.dll ] || cp $(DLL32_PATH)/libwinpthread-1.dll win32/
	@[ -f libstdc++-6.dll ] || cp $(DLL32_PATH)/libstdc++-6.dll win32/
	@[ -f libgcc_s_sjlj-1.dll ] || cp $(DLL32_PATH)/libgcc_s_sjlj-1.dll win32/

copy-dll64:
	@[ -f libwinpthread-1.dll ] || cp $(DLL64_PATH)/libwinpthread-1.dll win64/
	@[ -f libstdc++-6.dll ] || cp $(DLL64_PATH)/libstdc++-6.dll win64/
	@[ -f libgcc_s_seh-1.dll ] || cp $(DLL64_PATH)/libgcc_s_seh-1.dll win64/

clean:
	@rm -rf linux win32 win64

Compilation de plusieurs fichiers sources

Sources

Afin de simplifier les explications, on va se concentrer sur le <class>C++</class> et utiliser un fichier Makefile. Cette fois les sources seront un fichier principal (<path>main.cpp</path>) ainsi qu'un fichier de définition (<path>data.h</path>) et d'implémentation (<path>data.cpp</path>) d'une classe.

TextFileIcon16.png main.cpp
#include <iostream>
#include "data.hpp"

using namespace std;

int main()
{
	Data data;
	data.set( 99 );
	cout << "data: " << data.get() << endl;
	return 0;
}
TextFileIcon16.png data.h
#ifndef _DATA_HPP_
#define _DATA_HPP_

#include <iostream>

class Data
{
public:
	Data();
	~Data();
	void set( int number );
	int get() const;

protected:
	int _number;
};

int get();

#endif //_DATA_HPP_
TextFileIcon16.png data.cpp
#include "data.h"

Data::Data() :
	_number( 0 )
{
}

Data::~Data()
{
}

void Data::set( int number )
{
	_number = number;
}

int Data::get() const
{
	return _number;
}

Fichier Makefile

Cette fois on va définir un modèle générique afin de compiler chaque fichier source cpp en fichier objet et lier ces fichiers objet entre eux pour obtenir un exécutable.

Note.png
Fichiers temporaires
Les fichiers temporaires ne sont pas isolés dans leur répertoire d'accueil respectif, mais cela ne pose pas de problème de conflit entre les architectures.
TextFileIcon16.png Makefile
CXXFLAGS=-O0 -Wall -g
LDFLAGS=
CXXTARGET=data
 
DLL32_PATH := /usr/i686-w64-mingw32/sys-root/mingw/bin
DLL64_PATH := /usr/x86_64-w64-mingw32/sys-root/mingw/bin
 
linux: tree linux.exe

win: win32 win64

win32: tree win32.exe copy-dll32

win64: tree win64.exe copy-dll64

all: linux win

tree:
	@[ -d linux ] || mkdir linux
	@[ -d win32 ] || mkdir win32
	@[ -d win64 ] || mkdir win64

%.o: %.cpp $(CXXDEPS)
	@g++ -c -o $@ $< $(CXXFLAGS)
	
%.obj32: %.cpp $(CXXDEPS)
	@i686-w64-mingw32-g++ -c -o $@ $< $(CXXFLAGS)
	
%.obj64: %.cpp $(CXXDEPS)
	@x86_64-w64-mingw32-g++ -c -o $@ $< $(CXXFLAGS)

linux.exe: main.o data.o
	@g++ $(CXXFLAGS) -o linux/$(CXXTARGET) main.o data.o

win32.exe: main.obj32 data.obj32
	@i686-w64-mingw32-g++ $(CXXFLAGS) -o win32/$(CXXTARGET).exe main.obj32 data.obj32

win64.exe: main.obj64 data.obj64
	@x86_64-w64-mingw32-g++ $(CXXFLAGS) -o win64/$(CXXTARGET).exe main.obj64 data.obj64

copy-dll32:
	@[ -f libwinpthread-1.dll ] || cp $(DLL32_PATH)/libwinpthread-1.dll win32/
	@[ -f libstdc++-6.dll ] || cp $(DLL32_PATH)/libstdc++-6.dll win32/
	@[ -f libgcc_s_sjlj-1.dll ] || cp $(DLL32_PATH)/libgcc_s_sjlj-1.dll win32/

copy-dll64:
	@[ -f libwinpthread-1.dll ] || cp $(DLL64_PATH)/libwinpthread-1.dll win64/
	@[ -f libstdc++-6.dll ] || cp $(DLL64_PATH)/libstdc++-6.dll win64/
	@[ -f libgcc_s_seh-1.dll ] || cp $(DLL64_PATH)/libgcc_s_seh-1.dll win64/

clean:
	@rm -rf linux win32 win64 *.o *.obj32 *.obj64