CPP/CrossCompilation
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
Le fichier source en C++
#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 .
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
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.
#include <iostream>
#include "data.hpp"
using namespace std;
int main()
{
Data data;
data.set( 99 );
cout << "data: " << data.get() << endl;
return 0;
}
#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_
#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.
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