Skip to content

Instantly share code, notes, and snippets.

@CobaltXII
Last active February 24, 2024 16:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save CobaltXII/f6f49dd3217569b20879a5e059953544 to your computer and use it in GitHub Desktop.
Save CobaltXII/f6f49dd3217569b20879a5e059953544 to your computer and use it in GitHub Desktop.
Cross-Compiling SDL2 Programs for Windows from Linux

Cross-Compiling SDL2 Programs for Windows from Linux

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.

Installing and Using a Cross-Compiler

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!

Installing Third-Party Libraries to Use With a Cross-Compiler

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.

Using Third-Party Libraries With a Cross-Compiler

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.

Common Pitfalls

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment