I'll explain how to create programs that run on Windows (.exe
files) from a Linux machine. I'll explain how to compile C and C++, and how to make 32- and 64-bit programs. I'll also explain how to use third-party libraries (we'll use SDL2 here), and how to distribute the programs.
To cross-compile for another architecture, you first need to know what architecture you're compiling for. The important part here is the target triplet. It's typically in this format:
machine-vendor-operatingsystem
For more information, see the OSDev Wiki. You can find your native target triplet by typing gcc -dumpmachine
. On my machine, I get x86_64-linux-gnu
.
Note: you can prefix your native target triple onto any existing commands. For example, g++
is the same as x86_64-linux-gnu-g++
on my machine. I can check this:
cxii@bocks:~$ ls -l /usr/bin/g++
lrwxrwxrwx 1 root root 6 Aug 11 14:28 /usr/bin/g++ -> g++-13
cxii@bocks:~$ ls -l /usr/bin/x86_64-linux-gnu-g++
lrwxrwxrwx 1 root root 6 Aug 11 14:28 /usr/bin/x86_64-linux-gnu-g++ -> g++-13
First, the machine
part. This is the CPU architecture. Let's keep it simple: use x86_64
if you want to make a 64-bit program, and use i686
if you want to make a 32-bit program. x86_64
is backwards compatible, so you can run an i686
program anywhere (on 32- or 64-bit hosts). I'd recommend to either make just an x86_64
program (nobody uses 32-bit anymore) or both. Please don't make just an i686
program without also providing an x86_64
program.
Second, the vendor
part. We'll just write w64
here, since we want to cross-compile for Windows. I have no idea why it's w64
. I tried to find something that used w32
, with no luck, so we'll just stick with w64
. Use w64
even when using i686
.
Third, the operatingsystem
part. We'll just write mingw32
here. I really don't know why this is mingw32
and not mingw64
, but like the above, it just works. Anyways...
Now we know our target triple. To recap, if we want 32-bit programs, it's i686-w64-mingw32
. If we want 64-bit programs, it's x86_64-w64-mingw32
. Now, we can simply prefix this onto our commands to get a command which targets our target triple. For example: x86_64-w64-mingw32-g++
. Go ahead and install these programs if you don't have them already.
Your shell will probably tell you what packages you need to install, but I'll list them here anyways. It'll be in this format: {gcc|g++}-mingw-w64-{i686|x86-64}-{posix|win32}
. That's a lot. Basically, choose gcc
if you want to compile C, and g++
if you want to compile C++. Install both if you want both. Choose i686
or x86-64
based on if you want to make 32- or 64-bit programs. Note that it's a dash (-
) not an underscore (_
) in x86-64
(yet another discrepancy!). Choose posix
if you want C11/C++11 threads, and win32
if you don't. Hint: you probably want to choose posix
here.
For example, if I want to compile 64-bit C++ programs for Windows, I'd install g++-mingw-w64-x86-64-posix
. Phew!
We can try compiling a simple program for Windows:
cxii@bocks:~$ printf '#include <stdio.h>\nint main() {printf("Hello, world!\\n");}\n' > main.c
cxii@bocks:~$ cat main.c
#include <stdio.h>
int main() {printf("Hello, world!\n");}
cxii@bocks:~$ x86_64-w64-mingw32-gcc main.c -o main.exe
cxii@bocks:~$ wine main.exe
Hello, world!
In the /usr
directory you have /usr/bin
, /usr/include
, and /usr/lib
. These are the binary, include and library directories for your native platform. Inside /usr/include
, you'll find /usr/include/SDL2
, /usr/include/X11
, etc.
When you use gcc
or clang
, it looks in /usr/include
for headers (which makes sense, it's looking for the headers for your native platform, since you're compiling for your native platform). The same idea applies for library files.
Installing packages with apt
or similar will put all the relevant files into these directories. For example, if we consider SDL2, we can find it's headers in /usr/include/SDL2
. We can find it's library files in /usr/lib/x86_64-linux-gnu
. Why do we have to append the native target triple at the end here? I have no idea.
After installing a cross-compiler, you'll also have another directory, such as /usr/x86_64-w64-mingw32
. This directory contains /usr/x86_64-w64-mingw32/bin
, /usr/x86_64-w64-mingw32/include
, and /usr/x86_64-w64-mingw32/lib
. These are the binary, include, and library directories for the target platform, in this case, x86_64-w64-mingw32
.
Now, when you use x86_64-w64-mingw32-g++
or similar, it looks in /usr/x86_64-w64-mingw32/include
for headers. This makes sense, it's looking in the include directory for the specified target, not in the native include directory. The same idea applies for library files.
How do we actually install a third-party library into these directories though? Let's consider SDL2 as an example. There may be a way to do this with a package manager, but I don't know how to do that. Head over to their website. It'll take us to their GitHub releases page, and we can look for the mingw
development files. Download them, and we should see a file tree like this:
cxii@bocks:~/Downloads/SDL2-devel-2.28.5-mingw/SDL2-2.28.5$ ls -l
total 104
...
drwxr-xr-x 6 cxii cxii 4096 Oct 31 2018 i686-w64-mingw32
drwxr-xr-x 6 cxii cxii 4096 Oct 31 2018 x86_64-w64-mingw32
...
There are the two target triples we talked about earlier! If we enter x86_64-w64-mingw32
, we'll see the following directories:
cxii@bocks:~/Downloads/SDL2-devel-2.28.5-mingw/SDL2-2.28.5/x86_64-w64-mingw32$ ls -l
total 16
drwxr-xr-x 2 cxii cxii 4096 Nov 2 13:05 bin
drwxr-xr-x 3 cxii cxii 4096 Oct 31 2018 include
drwxr-xr-x 4 cxii cxii 4096 Nov 2 13:05 lib
...
And there are the three main directories we talked about earlier! It is now as simple as merging these three directories into /usr/x86_64-w64-mingw32
.
Note: SDL2 actually provides make cross
to automatically install itself for cross-compiling for you. Other libraries may do something similar. Personally, I prefer to do it myself, as these scripts have not always worked correctly.
Typically, pkg-config
is used to gather flags needed to build software that uses a third-party library. For example, with SDL2 you can write pkg-config sdl2 --cflags
to get the flags you need to pass to gcc
. You can also write pkg-config sdl2 --libs
to get the flags you need to pass to ld
. Here's some example output on the native machine:
cxii@bocks:~$ pkg-config sdl2 --cflags
-I/usr/include/SDL2 -D_REENTRANT
cxii@bocks:~$ pkg-config sdl2 --libs
-lSDL2
Clearly, this won't do for a cross-compilation, as it's outputting the native include directory (as it should). Not to mention that there are no Windows specific options, which we need. We need something like x86_64-w64-mingw32-pkg-config
(or i686-
) which we can use for this. And we're in luck, such a thing exists. It's part of the mingw-w64-tools
package. Let's try it:
cxii@bocks:~$ x86_64-w64-mingw32-pkg-config sdl2 --cflags
-I/x86_64-w64-mingw32/include -I/x86_64-w64-mingw32/include/SDL2 -Dmain=SDL_main
cxii@bocks:~$ x86_64-w64-mingw32-pkg-config sdl2 --libs
-L/x86_64-w64-mingw32/lib -lmingw32 -lSDL2main -lSDL2 -mwindows
Great, it works.
We can try compiling a simple SDL2 program for Windows:
cxii@bocks:~$ cat sdl2.c
#include <SDL2/SDL.h>
int main(int argc, char** argv)
{
SDL_Init(SDL_INIT_EVERYTHING);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Hello, world!", "Hello, world!", NULL);
}
cxii@bocks:~$ x86_64-w64-mingw32-gcc sdl2.c -o sdl2.exe $(x86_64-w64-mingw32-pkg-config sdl2 --cflags --libs)
cxii@bocks:~$ wine sdl2.exe
...
Make sure to package SDL2.dll
with your .exe
when distributing it. This doesn't need to be done when testing it through wine
so it's easy to miss. You can find SDL2.dll
in /usr/x86_64-w64-mingw32/bin/
or /usr/i686-w64-mingw32/bin/
.
Note: the reason x86_64-w64-mingw32-pkg-config
even works is because in /usr/x86_64-w64-mingw32/lib
there is another directory: /usr/x86_64-w64-mingw32/lib/pkgconfig
. This directory contains .pc
files, such as sdl2.pc
.
Sometimes when you distribute the .exe
file and try to run it under Windows (not wine
) you'll get an error mentioning something about libstdc++-6.dll
(or similar). There are two solutions, you can either link with these libraries statically, or distribute the .dll
files yourself.
If you want to link statically, pass -static-libgcc -static-libstdc++
to ld
. If you want to distribute the .dll
files yourself, look in /usr/lib/gcc/x86_64-w64-mingw32
or /usr/lib/gcc/i686-w64-mingw32
for the .dll
files you need.