4
votes

I'm building a C application on Windows using Msys2 and MingW.

This application will be shipped to Desktop Windows machines of end users who are not tech-savvy. I have two questions regarding this:

  1. What is the best practice way to ship a Windows program built using Msys64 and MingW, to a different Windows machine which doesn't have these installed? Some sources on the internet and here on SO say we have no choice but to distribute the exe file in the same directory along with the MingW dlls it depends on. Or, to statically link the exe with the MingW dlls. But, I'd like to make sure if these are indeed the standard ways to do so. It seems odd that MingW wouldn't have a better way.

  2. Suppose the said exe also at some point uses LoadLibrary API to dynamically load a dll (think plugin system). That dll was also built using MingW (and in this case of course will be shipped to end users along with the exe). Is there anything special I need to do to make sure that the dll will be loaded successfuly on a user machine without MingW installed?

  3. In addition to 2: does the said DLL need an import library (.lib) present during compilation on the development machine, and/or during launch on the end machine?

EDIT: Please let me clarify, I am not using MSYS2 as a command line shell. I am working directly with the Mingw gcc through the Windows cmd, and have simply obtained Mingw from inside the MSYS2 download directory.

2

2 Answers

4
votes

I assume this question is about MSYS2 and mingw-w64 , since there is no such thing as "msys64" .

MSYS2 offers three different target systems:

  • Windows 32-bit
  • Windows 64-bit
  • MSYS2

each of which have entirely separate source trees for their development tools and packages under the MSYS2 installation.

This is not to be confused with different build systems -- the build system can be either Win32 or Win64. For example if you chose Win64 as the build system and chose to install all three target systems then you will have have trees msys64/mingw32, msys64/mingw64, and msys64/usr

The MSYS2 installer creates startup scripts for each target that you installed.


If you target Win32 or Win64, then the compiled binaries (exe or dll) can be distributed as standalone products. You can do a static build with gcc or clang , producing a single standalone executable, or you can do a build that depends on DLLs which you distribute with your executable.

It is standard for binary distributions to include all files that they require and are not provided by the target operating system, this is not a peculiarity of mingw-w64.

The LoadLibrary search path is described in the MSDN documentation .

If you target MSYS2 then the resulting binary SHOULD be run under a MSYS2 shell. This target offers some POSIX features that are not supported directly by mingw-w64. It can be considered a fork of Cygwin. It would be possible to distribute a binary targeting MSYS2 along with a bunch of DLLs copied from the MSYS2 installation that you identify using dependency tracker.

2
votes

I cannot see any downside to this solution.
I am not much of a Windows user, but I must admit that the solution of implicitly finding dynamic libraries where stands the executable is of great help.
You put the whole content of your application (the executables and the dynamic libraries/plugins) anywhere and as soon as you manage to run the executable everything else will be found.

Of course, if you plan to deliver many different applications with a common subset of dynamic libraries, it could be better to put all theses libraries in a common place and adjust the PATH environment variable accordingly.
But it is not worth the effort for a single application.

In a mingw-w64 (not exactly Msys64 and MingW, but very close) based application (with plugins) that I delivered a few months ago, I just provided libgcc_s_seh-1.dll, libstdc++-6.dll and libwinpthread-1.dll as a complement to my own binaries and it worked without any problem.
Using objdump.exe -p my_program.exe (then recursively on the displayed result) helps finding the needed dynamic libraries (like ldd on Linux or otool -L on Macosx).

That's what I like in the mingw-like solution: it makes a native Windows application that does not depend on many other unusual components (that the user would have had to retrieve first).


There is no need to deal with some .lib files; building a .dll and linking against it is enough (see the example below).
It works exactly as we do on UNIX (with .so files).
I really don't know why Visual-C++ relies on a so complicated combination of .lib and .dll files...


I just re-tested with this simple example.

file prog.cpp

#include <windows.h>
#include <iostream>

__declspec(dllimport)
int
my_library_function(int arg);

int
main()
{
  std::cout << "~~~~ entering " << __func__ << " ~~~~\n";
  int result=my_library_function(123);
  std::cout << "result=" << result << '\n';
  std::cout << "~~~~ still in " << __func__ << " ~~~~\n";
  HINSTANCE lib=LoadLibrary("my_plugin.dll");
  if(lib)
  {
    FARPROC symbol=GetProcAddress(lib, "my_plugin_function");
    if(symbol)
    {
      int (*fnct)(int)=NULL;
      memcpy(&fnct, &symbol, sizeof(fnct));
      int result=fnct(123);
      std::cout << "result=" << result << '\n';
    }
    FreeLibrary(lib);
  }
  std::cout << "~~~~ leaving " << __func__ << " ~~~~\n";
  return 0;
}

file my_library.cpp

#include <iostream>

__declspec(dllexport)
int
my_library_function(int arg)
{
  std::cout << "~~~~ entering " << __func__ << " ~~~~\n";
  std::cout << "arg=" << arg << '\n';
  std::cout << "~~~~ leaving " << __func__ << " ~~~~\n";
  return 2*arg;
}

file my_plugin.cpp

#include <iostream>

extern "C" __declspec(dllexport)
int
my_plugin_function(int arg)
{
  std::cout << "~~~~ entering " << __func__ << " ~~~~\n";
  std::cout << "arg=" << arg << '\n';
  std::cout << "~~~~ leaving " << __func__ << " ~~~~\n";
  return 2*arg;
}

build process

==== compiling [opt=0] my_plugin.cpp ====
g++ -o my_plugin.o my_plugin.cpp -c   -g -O0  -MMD -pedantic -Wall -Wextra -Wconversion -Wno-unused -Wno-unused-parameter -Werror -Wfatal-errors -UNDEBUG  -std=c++17 -Wno-missing-braces -Wno-sign-conversion

==== linking [opt=0] my_plugin.dll ====
g++ -shared -o my_plugin.dll my_plugin.o   -g -O0

==== compiling [opt=0] my_library.cpp ====
g++ -o my_library.o my_library.cpp -c   -g -O0  -MMD -pedantic -Wall -Wextra -Wconversion -Wno-unused -Wno-unused-parameter -Werror -Wfatal-errors -UNDEBUG  -std=c++17 -Wno-missing-braces -Wno-sign-conversion

==== linking [opt=0] my_library.dll ====
g++ -shared -o my_library.dll my_library.o   -g -O0

==== compiling [opt=0] prog.cpp ====
g++ -o prog.o prog.cpp -c   -g -O0  -MMD -pedantic -Wall -Wextra -Wconversion -Wno-unused -Wno-unused-parameter -Werror -Wfatal-errors -UNDEBUG  -std=c++17 -Wno-missing-braces -Wno-sign-conversion

==== linking [opt=0] prog.exe ====
g++ -o prog.exe prog.o   -g -O0 -lmy_library

execution

C:\Work\PluginTest>prog.exe
~~~~ entering main ~~~~
~~~~ entering my_library_function ~~~~
arg=123
~~~~ leaving my_library_function ~~~~
result=246
~~~~ still in main ~~~~
~~~~ entering my_plugin_function ~~~~
arg=123
~~~~ leaving my_plugin_function ~~~~
result=246
~~~~ leaving main ~~~~

C:\Work\PluginTest>

This still works when the mingw64 directory (containing the toolchain) is renamed, as soon as the files libgcc_s_seh-1.dll, libstdc++-6.dll and libwinpthread-1.dll are placed in the same directory as prog.exe.
It even works when the prog.exe is launched by clicking (not from the command line); to see this I had to add an infinite loop at the end of the program to keep the window open long enough to see the displayed messages.