前言

很多人都觉得 Linux 相比于 Windows 而言更适合开发,但由于 Windows 在 PC 上占有量巨大,一些程序即使原本在 Linux 上开发的,最终往往需要部署到 Windows 环境。面对这样的跨平台需求,比较常见的操作是用宏来控制程序的编译:

#if defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64)
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif

DLL_EXPORT void func();

利用宏可以在不同的平台上生成不同的代码,从而让一个工程同时支持 Linux (gcc) 和 Windows (MSVC)。不过这种方法增加了编写程序时需要考虑的因素(系统相关或者编译器相关的代码需要特殊处理,甚至需要写两种实现),而且如果程序原本只考虑了 gcc,后面想移植 MSVC 可能还是要费点脑筋的。另外,MSVC 有时会存在一些莫名其妙的 bug,比如:

相比而言,我用 gcc 貌似就没碰上过 bug(也有可能时因为 Manjaro 滚动更新,bug 修的快)。

考虑到 MSVC 的额外工作量,以及对 gcc 的偏好,C++ 程序移植 Windows 其实还有另一种办法:MinGW。

MinGW 是什么

MinGW (Minimalist GNU for Windows) 是一种用于开发原生 Windows 应用的最小化 GNU 开发环境,可以理解为开发 Windows 程序的 gcc。MinGW 本身并不一定要运行在 Windows 下,Linux 上也可以通过 MinGW 工具链交叉编译 Windows 程序。

MSYS (Minimal SYStem) 是一系列 GNU 工具(bash、make、gawk、grep等)的集合(基于旧版 Cygwin),用于弥补 Windows cmd shell 的不足,让 MinGW 在 Windows 上更便于使用。

MSYS2 是一个独立的 MSYS 重写(因为 MinGW 和 MSYS 更新缓慢),基于新一代 Cygwin 和 MinGW-w64,提供更多 API 支持和 64 位应用开发,因此建议抛弃 MSYS 直接使用 MSYS2。

至于 Cygwin 是什么以及其它详细介绍,可参见相关官网和 Cygwin 和MinGW 的区别与联系是怎样的?@LiTuX 的回答

MSYS2 安装配置

本章节参考资料:

1. 下载安装

32位安装包:msys2-i686-latest.exe
64位安装包:msys2-x86_64-latest.exe

安装路径任意,但最好不要出现中文或空格(原因你懂的),本文假定你是64位机器,且安装路径默认(C:\msys64,32位的机器请自行替换为 C:\msys32)。安装完成后,如果 MSYS2 shell 自动打开,可以先关掉。

在安装路径或开始菜单中,可以看到有三个 MSYS2 shell:MSYS2 (msys2.exe)、MinGW 32-bit (mingw32.exe) 和 MinGW 64-bit (mingw64.exe),每个 shell 对应于一个编译 Windows 程序的环境(子系统)。MSYS2 使用一个模拟层提供 POSIX 编译支持,MinGW 32/64-bit 用于编译32/64位的原生 Windows 应用,理论上 MinGW 32/64 编译的程序性能优于 MSYS2,且依赖项更少。参见 MSYS2 introduction。三个 shell 只是默认的环境变量不同,并且支持各自不同配置:
不同 shell 的默认环境变量

2. 配置 pacman 源

MSYS2 和 Arch Linux/Manjaro 一样采用 Pacman 作为包管理系统,这里可以简单调整一下其数据库源,提高下载速度。打开路径 C:\msys64\etc\pacman.d,编辑所有 mirrorlist.* 文件(64位机器上有三个,扩展名分别是 mingw32、mingw64 和 msys),保留其中一个国内源:清华源(mirrors.tuna.tsinghua.edu.cn)、中科大源(mirrors.ustc.edu.cn)、或者北理源(mirror.bit.edu.cn),其它全部注释掉:

...

## Primary
## msys2.org
#Server = http://repo.msys2.org/msys/$arch/
#Server = https://sourceforge.net/projects/msys2/files/REPOS/MSYS2/$arch/
#Server = https://www2.futureware.at/~nickoe/msys2-mirror/msys/$arch/
#Server = https://mirror.yandex.ru/mirrors/msys2/msys/$arch/
#Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/msys/$arch/
Server = http://mirrors.ustc.edu.cn/msys2/msys/$arch/
#Server = http://mirror.bit.edu.cn/msys2/msys/$arch/
#Server = https://mirror.selfnet.de/msys2/msys/$arch/

3. 更新 MSYS2 系统

打开 MSYS2 (对于安装/更新软件包而言,MSYS、MinGW32、MinGW64 任意一个 shell 都行),运行命令 pacman -Syyu 进行系统和软件包更新。然后重启 MSYS2,运行 pacman -Su,确保不再有更新。

注:对于 pacman 命令,-S 表示与远程仓库同步(sync)的操作,-u 表示系统更新(即更新全部软件包),-y 表示刷新本地的软件包数据库,两个 y 为强制刷新所有包的信息。

这里有个小提示,MSYS2 shell (mintty) 中默认的复制粘贴快捷键是 Shift/Ctrl+Ins,如果想使用 Ctrl+Shift+C/V 快捷键,可以在窗口中右击 -> Options -> Keys,勾选“Ctrl+Shift+letter shortcuts”。

4. 安装 GCC 和常用编译工具

运行以下命令,如果遇到选择包或者确认安装,一路回车即可:

pacman -S --needed base-devel git vim \
      mingw-w64-i686-toolchain \
      mingw-w64-x86_64-toolchain \
      mingw-w64-i686-cmake \
      mingw-w64-x86_64-cmake

普通的包(比如上面的 base-devel、git、vim)会被安装到 C:\msys64\usr\bin 路径下,对应 MSYS;以 mingw-w64-i686 开头的包安装在 C:\msys64\mingw32\bin 中,对应 MinGW32;以 mingw-w64-x86_64 开头的包安装在 C:\msys64\mingw64\bin 中,对应 MinGW64。

然后,就可以使用 cmakegccg++ 等命令来编译 C++ 程序了,分别在 MinGW32/64 shell 中编译32位或64位的 Windows 程序。比如用 cmake:

cd /your-project-dir
mkdir build && cd build
cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=release ..
cmake --build .

5. 第三方库

MSYS2 的包仓库中有着非常丰富的预编译库包,而且版本都是最新的,比如 opencv、boost、eigen3、ceres-solver、vtk、qt5 等。可以用 pacman -Ss the_library 命令来搜索一个库是否有预编译包,比如要找 opencv:

$ pacman -Ss opencv
mingw32/mingw-w64-i686-opencv 4.4.0-1
    Open Source Computer Vision Library (mingw-w64)
mingw64/mingw-w64-x86_64-opencv 4.4.0-1
    Open Source Computer Vision Library (mingw-w64)

然后用 pacman -S mingw-w64-x86_64-opencv 命令安装64位的预编译包。

如果想自己编译安装一个库,注意一下安装的目标路径即可(比如32位的库要安装到 /mingw32 路径下,不要安装到默认路径C:/Program Files (x86)/ 中)。

6. 配置环境变量(可选)

Windows 方面

MinGW64 编译出的程序依赖 C:\msys64\mingw64\bin 中的库(32位类似),如果想更方便地在 Windows 中运行编译好的程序,可以将 C:\msys64\mingw64\bin 和 C:\msys64\mingw32\bin 两个路径加入到 Windows 环境变量中。

这里要注意的是,MinGW32/64 中安装的程序有可能与 Windows 中的程序冲突(比如 python),如果遇到这样的情况,可以在环境变量里面把 MinGW 的路径往后放,这样 Window 系统中就会优先使用 MSYS 外的程序。

MSYS 方面

首先关于 MSYS shell 的启动要注意一点:直接从安装路径中双击 exe 启动某个 shell 和从开始菜单中的快捷方式启动有所不同。以 MinGW64 shell 为例,双击 C:\msys64\mingw64.exe 启动时,将使用 C:\msys64\mingw64.ini 中的配置。而如果使用开始菜单中的 MSYS2 MinGW 64-bit 快捷方式启动,则会调用 C:\msys64\msys2_shell.cmd 启动 shell,忽略 mingw64.ini 中的配置!

默认情况下,MSYS 的三个 shell 只从 Windows PATH 继承系统相关的环境变量(C:\Windows\…),如果想让 MSYS shell 继承全部 Windows PATH 环境变量(这样能够更方便地在 MSYS shell 中调用 Windows 中的程序),可以选择如下方法之一:

  • 在 Windows 系统环境变量中添加 MSYS2_PATH_TYPE=inherit
  • 在调用 msys2_shell.cmd 时使用 -use-full-path 参数(修改相关的快捷方式)
  • 在 msys2_shell.cmd 中取消一行的注释(即删除行首的 rem):set MSYS2_PATH_TYPE=inherit(对 exe 直接启动无效)
  • 在相应的 ini 文件(比如 msys2.ini)中,取消 MSYS2_PATH_TYPE=inherit 这一行的注释(对 msys2_shell.cmd 启动无效)

其实在 MSYS 中继承全部 Windows PATH 在绝大部分情况下都是没必要的,反而很容易导致各种冲突问题,所以不建议这么做。

最后:关于程序执行效率

MinGW 编译出来的是原生 Windows 程序(没有 Linux 模拟层),理论上不会因为程序移植带来性能损失,网上很多人的经验观点也是这样:

然而,我在同一台电脑上测试自己的一个算法平均时耗如下:

系统时间消耗
Manjaro (VMware虚拟机)9.53 ms
Ubuntu 20.04 (VMware虚拟机)8.31 ms
Ubuntu 20.04 (WSL2)7.62 ms
MinGW12.67 ms
Win7 虚拟机中的 MinGW14.38 ms

这个算法没有在 MSVC 中测试,但预计 MSVC 的执行效率应该不会比 WSL2 慢吧(微软亲儿子还能不如 Linux 子系统?)。可见 MinGW 可能并不能达到程序最高的执行效率,至少对某些算法而言是这样的,实际使用中需要关注这一点。另外想说一句,WSL2 真香!

Logo

更多推荐