将使用 MSYS2 和 MingW 构建的程序发送给最终用户的正确方法是什么?
What is the correct way to ship a program built using MSYS2 and MingW to end users?
我正在使用 Msys2 和 MingW 在 Windows 上构建一个 C 应用程序。
此应用程序将发送到不精通技术的最终用户的桌面 Windows 机器上。我有两个问题:
将使用 Msys64 和 MingW 构建的 Windows 程序发送到未安装这些的不同 Windows 机器的最佳实践方法是什么?互联网上和 SO 上的一些消息来源说我们别无选择,只能将 exe
文件与它所依赖的 MingW dll
s 一起分发到同一目录中。或者,使用 MingW dll
静态 link exe
。但是,我想确定这些是否确实是这样做的标准方法。 MingW 没有更好的方法似乎很奇怪。
假设上述 exe
也在某些时候使用 LoadLibrary
API 动态加载 dll
(想想插件系统)。 dll
也是使用 MingW
构建的(在这种情况下,当然会与 exe
一起发送给最终用户)。我需要做些什么来确保 dll
在没有安装 MingW 的用户机器上成功加载?
除了 2:上述 DLL 是否需要在开发机器上编译期间存在导入库 (.lib
),在终端机器上启动期间 and/or?
编辑:请让我澄清一下,我 不是 使用 MSYS2 作为命令行 shell。我通过 Windows cmd
直接使用 Mingw gcc
,并且只是从 MSYS2 下载目录中获取 Mingw。
我看不出这个解决方案有任何缺点。
我不是一个 Windows 用户,但我必须承认解决方案
隐式查找可执行文件所在的动态库
很有帮助。
您将应用程序的全部内容(可执行文件和
动态 libraries/plugins) 随处随你管理
运行 可执行文件将找到其他所有内容。
当然,如果您计划交付许多不同的应用程序
使用动态库的公共子集,它可能会更好
将所有这些库放在一个共同的地方并调整
相应的 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 的解决方案:它使本地
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;
}
文件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.dll
被重命名,
libstdc++-6.dll
和 libwinpthread-1.dll
放在
与 prog.exe
.
相同的目录
它甚至可以在 prog.exe
通过单击(不是
从命令行);看到这个我必须添加一个无限循环
在程序结束时保持 window 打开足够长的时间
查看显示的消息。
我认为这个问题是关于 MSYS2 和 mingw-w64 的,因为没有 "msys64" 这样的东西。
MSYS2 提供三种不同的 target 系统:
- 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
搜索路径描述为 in the MSDN documentation .
如果您以 MSYS2 为目标,那么生成的二进制文件应该在 MSYS2 shell 下 运行。该目标提供了一些 POSIX 特性,mingw-w64 不直接支持这些特性。它可以被认为是 Cygwin 的一个分支。可以分发针对 MSYS2 的二进制文件以及从您使用依赖项跟踪器识别的 MSYS2 安装复制的一堆 DLL。
我正在使用 Msys2 和 MingW 在 Windows 上构建一个 C 应用程序。
此应用程序将发送到不精通技术的最终用户的桌面 Windows 机器上。我有两个问题:
将使用 Msys64 和 MingW 构建的 Windows 程序发送到未安装这些的不同 Windows 机器的最佳实践方法是什么?互联网上和 SO 上的一些消息来源说我们别无选择,只能将
exe
文件与它所依赖的 MingWdll
s 一起分发到同一目录中。或者,使用 MingWdll
静态 linkexe
。但是,我想确定这些是否确实是这样做的标准方法。 MingW 没有更好的方法似乎很奇怪。假设上述
exe
也在某些时候使用LoadLibrary
API 动态加载dll
(想想插件系统)。dll
也是使用MingW
构建的(在这种情况下,当然会与exe
一起发送给最终用户)。我需要做些什么来确保dll
在没有安装 MingW 的用户机器上成功加载?除了 2:上述 DLL 是否需要在开发机器上编译期间存在导入库 (
.lib
),在终端机器上启动期间 and/or?
编辑:请让我澄清一下,我 不是 使用 MSYS2 作为命令行 shell。我通过 Windows cmd
直接使用 Mingw gcc
,并且只是从 MSYS2 下载目录中获取 Mingw。
我看不出这个解决方案有任何缺点。
我不是一个 Windows 用户,但我必须承认解决方案
隐式查找可执行文件所在的动态库
很有帮助。
您将应用程序的全部内容(可执行文件和
动态 libraries/plugins) 随处随你管理
运行 可执行文件将找到其他所有内容。
当然,如果您计划交付许多不同的应用程序
使用动态库的公共子集,它可能会更好
将所有这些库放在一个共同的地方并调整
相应的 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 的解决方案:它使本地 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;
}
文件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.dll
被重命名,
libstdc++-6.dll
和 libwinpthread-1.dll
放在
与 prog.exe
.
相同的目录
它甚至可以在 prog.exe
通过单击(不是
从命令行);看到这个我必须添加一个无限循环
在程序结束时保持 window 打开足够长的时间
查看显示的消息。
我认为这个问题是关于 MSYS2 和 mingw-w64 的,因为没有 "msys64" 这样的东西。
MSYS2 提供三种不同的 target 系统:
- 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
搜索路径描述为 in the MSDN documentation .
如果您以 MSYS2 为目标,那么生成的二进制文件应该在 MSYS2 shell 下 运行。该目标提供了一些 POSIX 特性,mingw-w64 不直接支持这些特性。它可以被认为是 Cygwin 的一个分支。可以分发针对 MSYS2 的二进制文件以及从您使用依赖项跟踪器识别的 MSYS2 安装复制的一堆 DLL。