使用MSYS2和MingW构建的程序正确的发布方式是什么?

8

我正在使用Msys2和MingW在Windows上构建一个C应用程序。

该应用程序将被部署到不精通技术的桌面Windows用户的机器上。我有两个问题:

  1. 使用Msys64和MingW构建的Windows程序如何最佳实践地部署到没有这些工具的其他Windows机器上?互联网上的一些来源和stackoverflow上说我们别无选择,只能将exe文件与它依赖的MingW dll放在同一个目录中分发,或者静态链接exe和MingW dll。但是,我想确保这确实是标准方法。看来MingW没有更好的方法似乎很奇怪。

  2. 假设该exe在某个点上也使用LoadLibrary API动态加载一个dll(类似插件系统)。该dll也是使用MingW构建的(在这种情况下当然会随exe一起提供给最终用户)。我需要做些特别的事情来确保在没有安装MingW的用户机器上成功加载dll吗?

  3. 除2之外:在开发机器上编译时和/或在最终机器上启动时,该DLL是否需要导入库(.lib)存在?

编辑:请让我澄清,我没有将MSYS2用作命令行shell。我直接通过Windows cmd与Mingw的gcc一起工作,并从MSYS2下载目录内获取了Mingw。

2个回答

7
我假设这个问题是关于MSYS2和mingw-w64的,因为没有“msys64”的东西。
MSYS2提供了三个不同的目标系统:
- Windows 32位 - Windows 64位 - MSYS2
每个系统都有完全独立的源树,用于其在MSYS2安装下的开发工具和软件包。
这与不同的构建系统不同--构建系统可以是Win32或Win64。例如,如果您选择Win64作为构建系统并选择安装所有三个目标系统,则会有树msys64/mingw32、msys64/mingw64和msys64/usr。
MSYS2安装程序为您安装的每个目标创建启动脚本。
如果你的目标是Win32或Win64,那么编译后的二进制文件(exe或dll)可以作为独立产品进行分发。你可以使用gcc或clang进行静态构建,生成单个独立可执行文件,也可以构建依赖于DLL的版本,并将其与可执行文件一起分发。
二进制发行版通常包括所有它们所需的文件,并且这不是mingw-w64的特殊情况。 LoadLibrary搜索路径的描述在MSDN文档中有说明
如果你的目标是MSYS2,则生成的二进制文件应该在MSYS2 shell下运行。该目标提供了一些POSIX功能,这些功能不能直接由mingw-w64支持。它可以被认为是Cygwin的一个分支。你可以通过使用依赖跟踪器来确定从MSYS2安装中复制的一堆DLL,并将针对MSYS2的二进制文件与它们一起分发。

你能指定依赖项跟踪器吗?我的意思是,我怎么知道需要哪些 DLL 文件? - Jatin Parmar
1
@JatinParmar https://github.com/lucasg/Dependencies - M.M

3
我看不出这种解决方案有什么缺点。
我不是一个经常使用Windows的用户,但我必须承认,隐式查找可执行文件所在位置的动态库的解决方案非常有帮助。
您可以将应用程序的全部内容(可执行文件和动态库/插件)放在任何地方,只要成功运行可执行文件,其他所有内容都会被找到。
当然,如果您计划提供许多不同的应用程序并共享一组公共的动态库,则最好将所有这些库放在一个公共位置,并相应地调整PATH环境变量。
但对于单个应用程序来说,这样做并不值得。
几个月前,我交付了一个基于mingw-w64(不完全是Msys64和MingW,但非常接近)的应用程序(带有插件),我只提供了libgcc_s_seh-1.dll、libstdc++-6.dll和libwinpthread-1.dll作为我的二进制文件的补充,它可以正常工作。
使用objdump.exe -p my_program.exe(然后递归地在显示的结果上)有助于找到所需的动态库(就像Linux上的ldd或Macosx上的otool -L一样)。
这就是我喜欢mingw-like解决方案的原因:它可以创建一个本地的Windows应用程序,不依赖于许多其他不寻常的组件(用户必须首先检索这些组件)。
没有必要处理一些.lib文件;构建一个.dll并链接就足够了(请参见下面的示例)。
它与我们在UNIX上使用.so文件完全相同。
我真的不知道为什么Visual-C++会依赖于如此复杂的.lib.dll文件组合...
我刚刚使用这个简单的示例进行了重新测试。
文件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;
}

文件 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;
}

构建过程
==== 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

执行

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>

即使mingw64目录(包含工具链)更名,只要将libgcc_s_seh-1.dlllibstdc++-6.dlllibwinpthread-1.dll文件放置在与prog.exe相同的目录中,此方法仍然有效。
即使通过点击(而非从命令行)启动prog.exe,它也可以正常运行。为了观察到显示的消息,我不得不在程序末尾添加一个无限循环来保持窗口打开足够长的时间。


嘿,感谢您的回答。那么根据您的经验,使用Mingw编译并动态加载(又称“LoadLibrary”)的DLL应该能够在不同的非Mingw机器上运行,只要加载“exe”文件在目录中附近有MingW DLL即可? - Aviv Cohn
据我所记,是的(至少在mingw-w64上)。我只是在本地解压缩了mingw-w64(系统不知道安装情况,我甚至可以重命名或删除此安装目录),因此测试的配置与未安装工具的客户端相同。一旦我重新启动到Windows,我会再次尝试(已经很久了),然后回来告诉你。 - prog-fh
@AvivCohn 我刚刚检查了一下(请参见编辑),它的工作方式正如我记得的那样。请注意,我使用的是mingw-w64而不是msys64+mingw(可能存在一些细微差异)。 - prog-fh
@AvivCohn,对于你在问题中后来添加的第三点(关于.lib文件),已经进行了编辑。 - prog-fh
你好,再次感谢您提供详细的输入。我有一个快速问题:当我使用Dependency Walker查找使用Mingw-w64编译的GCC编译的.exe的依赖项时,它不显示对libgcc*的依赖性 - 即使几乎所有使用GCC编译的程序都应该依赖于libgcc*。 有什么想法吗? - Aviv Cohn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接