113 lines
5.6 KiB
Markdown
113 lines
5.6 KiB
Markdown
|
---
|
||
|
title: "Collecting the DLLs Required by an MSYS2 Binary"
|
||
|
date: 2022-04-21T09:27:15Z
|
||
|
---
|
||
|
|
||
|
After many unsuccessful attempts of writing a third blog post, I just wanted to
|
||
|
use this opportunity to share something useful I found after dabbling with
|
||
|
porting an _SDL2_ app to _Windows_ via _MSYS2 MinGW x64_.
|
||
|
|
||
|
TL;DR: You can collect all the _MinGW_ DLLs your EXE file needs like this:
|
||
|
|
||
|
ldd my-cool-program.exe | grep /mingw64 | awk '{print $3}' | xargs -i cp {} .
|
||
|
|
||
|
You see, in the magical Linux realm you'll never really worry about shipping
|
||
|
programs in binary form. A nice tarball with a well documented build system is
|
||
|
often regarded as good enough. If your program is actually used by people, its
|
||
|
likely that distributions pick it up into their repositories. Or you use stuff
|
||
|
like _Flatpak_ or _AppImage_ if you want to provide binaries yourself. But
|
||
|
over in Windows-land providing .exe files is a normal part of distributing your
|
||
|
programs.
|
||
|
|
||
|
Now, if you use any special libraries they are often dynamically linked and
|
||
|
their code is stored externally to your .exe as a .dll. Some .dll files are
|
||
|
simply stored somewhere in System32 and are thus available everywhere, but
|
||
|
others have to be shipped manually alongside your program.
|
||
|
|
||
|
---
|
||
|
|
||
|
Now let me go over a short tangent praising the efforts the developers of
|
||
|
_MinGW64_ have made. I wrote a little game in C using Lua and SDL2. On Linux,
|
||
|
building that game was simple. I'd use GNU/Make, _pkg-config_ and my distro's
|
||
|
package manager to collect the dependencies and tie everything together. I'd
|
||
|
dreaded the Windows port but eventually gave it a shot. And holy shit. _MSYS2_
|
||
|
provides EXACTLY THE SAME workflow!
|
||
|
|
||
|
The packages' names are a bit more arcane sounding (e.g.:
|
||
|
`mingw-w64-x86_64-gcc`, `mingw-w64-x86_64-SDL2`, `mingw-w64-x86_64-lua`),
|
||
|
because MSYS2 provides multiple toolchains and thus the packages have to be
|
||
|
more explicit in their naming. But after installing everything and running
|
||
|
`make` I had a working .exe file! Well... It was working after I fixed a
|
||
|
segfault that I didn't catch under Linux anyways. But that one's on me.
|
||
|
|
||
|
Running the .exe in MSYS2's shell worked fine, but starting it outside of the
|
||
|
shell resulted in most DLLs being unavailable. That's because, all non-system
|
||
|
DLLs are not provided to the program by default. That includes stuff like Lua's
|
||
|
or SDL2's DLL files. They are stored in the path `/mingw64/bin/`. Now, you
|
||
|
COULD go ahead and manually copy them, but why do that when that task can be
|
||
|
automated?
|
||
|
|
||
|
---
|
||
|
|
||
|
MinGW64 provides a version of the `ldd` program, which spits out all the
|
||
|
dynamically linked libraries required by a program. Its output may look
|
||
|
something like this:
|
||
|
|
||
|
$ ldd my-cool-game.exe
|
||
|
ntdll.dll => /c/Windows/SYSTEM32/ntdll.dll (0x7ffa09e30000)
|
||
|
KERNEL32.DLL => /c/Windows/System32/KERNEL32.DLL (0x7ffa09d30000)
|
||
|
KERNELBASE.dll => /c/Windows/System32/KERNELBASE.dll (0x7ffa075f0000)
|
||
|
msvcrt.dll => /c/Windows/System32/msvcrt.dll (0x7ffa08c00000)
|
||
|
SHELL32.dll => /c/Windows/System32/SHELL32.dll (0x7ffa08cb0000)
|
||
|
msvcp_win.dll => /c/Windows/System32/msvcp_win.dll (0x7ffa07550000)
|
||
|
ucrtbase.dll => /c/Windows/System32/ucrtbase.dll (0x7ffa07d90000)
|
||
|
USER32.dll => /c/Windows/System32/USER32.dll (0x7ffa09690000)
|
||
|
win32u.dll => /c/Windows/System32/win32u.dll (0x7ffa07d60000)
|
||
|
GDI32.dll => /c/Windows/System32/GDI32.dll (0x7ffa08a50000)
|
||
|
gdi32full.dll => /c/Windows/System32/gdi32full.dll (0x7ffa078c0000)
|
||
|
SDL2_mixer.dll => /mingw64/bin/SDL2_mixer.dll (0x7ff9f7fd0000)
|
||
|
lua54.dll => /mingw64/bin/lua54.dll (0x7ff9f0e80000)
|
||
|
libwinpthread-1.dll => /mingw64/bin/libwinpthread-1.dll (0x7ff9f7210000)
|
||
|
SDL2.dll => /mingw64/bin/SDL2.dll (0x7ff9e39f0000)
|
||
|
(...)
|
||
|
|
||
|
Most referenced DLLs reside in Window's System32 folder. They are provided by
|
||
|
the system and will always be there. So you don't have to worry about providing
|
||
|
them. The DLLs in `/mingw64/bin/...` however need to be placed alongside your
|
||
|
.exe file if people should be able to run your game via double-clicking it.
|
||
|
|
||
|
Cool, so that's where our automation can begin! First step is super simple:
|
||
|
let's filter out the non-system DLLs using `grep`:
|
||
|
|
||
|
ldd my-cool-program.exe | grep /mingw64
|
||
|
|
||
|
Might not be _super_ robust, but I _highly_ doubt that the string "/mingw64"
|
||
|
will ever show up in a Windows system DLL's path.
|
||
|
|
||
|
Next, we only care about the DLL's path. So we use `awk` to cut out that
|
||
|
portion of the lines. If we regard the spaces as delimiting characters of text
|
||
|
columns, the full path is in the third column (The first is just the filename,
|
||
|
and the second is "=>"). The `awk` command `awk {print $3}` gives us this third
|
||
|
column, so we can just append it to our shell command:
|
||
|
|
||
|
ldd my-cool-program.exe | grep /mingw64 | awk '{print $3}'
|
||
|
|
||
|
By now, our shell command spits our a list of full paths of all non-system DLLs
|
||
|
dynamically linked to our program. Cool! But we want to automate the whole
|
||
|
thing, so lets add a final call to `xargs`, to copy all files in this list into
|
||
|
our current directory. Here, I'll use `xargs -i cp {} .`. The `-i cp {} .`
|
||
|
parameter means that, for every file in the list, we call `cp`, pass the DLL's
|
||
|
path as the first parameter and the target directory `.` as the second
|
||
|
parameter.
|
||
|
|
||
|
Here is the final call:
|
||
|
|
||
|
ldd my-cool-program.exe | grep /mingw64 | awk '{print $3}' | xargs -i cp {} .
|
||
|
|
||
|
---
|
||
|
|
||
|
Cool! Now go put this in your CI script and automate packaging your Windows
|
||
|
releases. Or, if you're like me, make exactly one release and wonder why you
|
||
|
spent so much time on figuring this out when you could have just collected the
|
||
|
files manually once and then forget about it ARGH
|