最近正好在给一个国密改造项目做Windows端适配,需要在GmSSL上同时测两套编译器工具链——MSVC和MinGW。说实话,GmSSL这个库本身在Linux下用gcc编译很顺畅,但到了Windows平台,坑就一个接一个冒出来了。前前后后花了差不多三天时间,把两套编译方案都彻底趟了一遍,从环境配置、依赖安装到产物的链接与使用,踩了不少雷,也积累了一些实在的经验。这篇文章就把整个编译过程完整记录下来,包括每一步的命令、参数选择的依据、常见报错的处理,以及两种编译器产物的实际差异。如果你正在Windows上编译GmSSL,或者准备用MSVC/MinGW交叉构建其他C/C++项目,这篇应该能帮你省下不少排查时间。
1. 编译前的准备:搞清GmSSL的构建体系
1.1 GmSSL到底是什么,为什么在Windows上编译会绕弯子
GmSSL是一个开源的密码算法库,核心定位是国密算法(SM2、SM3、SM4等)的标准实现和扩展,同时也兼容了大部分OpenSSL的功能接口。它最早从OpenSSL派生而来,代码结构上继承了OpenSSL的Configure + Makefile传统,但后来也逐步接入了CMake构建体系。这种“双轨制”在Linux下问题不大,到了Windows上就容易让人犯迷糊——你既可以用OpenSSL那套Perl脚本配置,也可以用CMake生成工程,选哪条路直接决定了后面编译器工具链的匹配难度。
实际使用场景上,GmSSL常见的用途有两类:一类是做国密SSL通信,比如替换Nginx里的OpenSSL模块;另一类是纯粹的密码运算,比如对数据进行SM2签名、SM4加密。在Windows上编译GmSSL,绝大多数人是冲着第二类来的——在自己的Windows客户端程序里调用国密算法,这就需要把GmSSL编成能被MSVC或MinGW链接的库文件。
我这次编译的目标很明确:在Windows x64环境下,分别用MSVC和MinGW产出GmSSL的静态库/动态库,并保证能被对应的编译器正常链接调用。这里必须说明一个关键点:MSVC和MinGW的编译产物是不能混用的。MSVC编出来的.lib和.dll,MinGW链接器不一定认;反过来MinGW编的.a,MSVC也经常接不上。这背后的本质是COFF格式和导入库格式的差异,后面会详细展开。
1.2 Windows编译GmSSL的依赖条件清单
在动手编译之前,务必先确认环境里的依赖是否齐全。GmSSL在Windows上的编译依赖主要有三块:
- Perl:GmSSL的Configure脚本是Perl写的,需要ActivePerl或者Strawberry Perl,推荐Strawberry Perl,因为它自带MinGW环境,两个工具链都能兼顾。
- C编译器:MSVC的话装Visual Studio Build Tools(或者完整VS);MinGW的话装MinGW-w64。
- NASM:用于x86/x64汇编代码的编译,GmSSL的SM4、SM2等核心算法有汇编优化实现,如果没有NASM会退回到C语言实现,性能差不少。
提示:NASM版本不要太旧,至少1.4以上,否则某些指令集优化代码会编译报错。
如果你打算用CMake方式编译,需要CMake 3.10以上版本。建议直接把CMake、Git、Strawberry Perl一次性装齐。环境变量方面,CMake和Git安装时勾选“Add to PATH”,Perl一般会自动配好。NASM需要手动把安装路径加到PATH里。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 两种编译器工具链:MSVC和MinGW的差异与选型逻辑
2.1 编译器和运行时本质上就不是一家人
MSVC是微软官方的C/C++编译器,配套的运行时库是MSVCRT或UCRT,产出的静态库格式是.lib,动态库导入库也是.lib,但内部是COFF格式。MinGW是GCC的Windows移植版,运行时依赖通常是msvcrt.dll(旧版)或者UCRT(新版),静态库格式是.a,动态库导入库也是.a。
这里有一个经常被踩的坑:很多人以为MinGW能用MSVC编出来的.lib文件,或者MSVC能链接MinGW的.a,实际基本不行。由于C++名字修饰、结构体内存对齐策略以及运行时的差异,跨编译器链接大概率会出现unresolved external symbol。所以编译之前一定要想清楚你自己的程序用什么编译器,GmSSL就用什么编译器编。这不是偷懒,而是C/C++生态里的底层规则。
2.2 从静态库和动态库的选择看两种方式的区别
MSVC和MinGW都支持静态链接和动态链接,但选择逻辑不太一样:
- MSVC静态库:GmSSL编出来是GmSSL.lib,链接时把全部代码打包进你的exe,部署简单,不需要带DLL。缺点是exe体积大,且如果多个模块都用同一个静态库,会有重复代码。
- MinGW静态库:编出来是libGmSSL.a或GmSSL.a,作用和MSVC静态库一样。但MinGW的静态库默认会把libgcc等运行时也静态链进去,文件更大。
- 动态库:MSVC生成gmssl.dll + GmSSL.lib导入库,MinGW生成gmssl.dll + libgmssl.dll.a导入库。动态方式体积小,但程序发布时需把DLL一并带上。
实际项目里我推荐静态库——国密库本身依赖不多,静态编译后部署省心,尤其做Windows桌面工具时不用处理DLL路径问题。
选编译器时还要考虑你后续的开发环境。如果主程序用Visual Studio开发,那无脑选MSVC;如果用Qt配合MinGW开发(Qt的MinGW套件很常见),那选MinGW更顺。
3. MSVC编译GmSSL的完整流程
3.1 环境准备:VS Build Tools、Perl、NASM一个都不能少
MSVC方式编译GmSSL,我推荐直接装Visual Studio Build Tools而不是完整版VS,体积小不少。安装时勾选“使用C++的桌面开发”工作负载即可。装完后在开始菜单找到“x64 Native Tools Command Prompt for VS 2022”并打开——注意一定用这个命令行窗口,因为它已经帮你配置好了MSVC的PATH、INCLUDE、LIB环境变量。自己开普通cmd再手动调vcvarsall.bat也行,但容易出错。
接着检查Perl和NASM:
bash复制perl -v
nasm -v
如果没有输出,说明环境变量没配好。Perl用了Strawberry Perl的话,在cmd里直接能用。NASM需要手动加PATH。
3.2 Configure + nmake 与 CMake 两条路线怎么选
GmSSL源码里有Configure脚本(OpenSSL风格),也支持CMake。用MSVC编译时,到底走哪条路?
实测下来,建议走CMake路线,原因有两个:第一,CMake能自动适配VS的工具链,省去手动指定编译器参数的麻烦;第二,GmSSL的CMakeLists对MSVC的支持更成熟,开箱即用。
操作步骤非常直接。在源码根目录打开x64 Native Tools命令行,执行:
bash复制mkdir build-msvc
cd build-msvc
cmake .. -G "Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX=D:\libs\gmssl-msvc -DBUILD_SHARED_LIBS=OFF
cmake --build . --config Release --parallel 8
cmake --install .
逐条解释一下这些参数:
-G "Visual Studio 17 2022"指定用VS2022的工程生成器。如果你装的是VS2019,要改成“Visual Studio 16 2019”。-A x64指定目标架构为x64。注意VS生成器默认可能是Win32,不指定的话编译出来是32位库。-DCMAKE_INSTALL_PREFIX是安装目录,把头文件和库统一放到一个干净目录,方便后续引用。-DBUILD_SHARED_LIBS=OFF表示编译静态库。如果要DLL,改成ON。--config Release在CMake的VS生成器下必须显式指定配置类型。--parallel 8用8线程并行编译,机器配置低就改成4或2。
编译完成后,在D:\libs\gmssl-msvc下会看到include目录和lib目录。lib里是GmSSL.lib静态库,直接可以被MSVC链接。
3.3 关键参数解析:为什么要关闭共享库、怎么开启国密算法模块
这里有个容易踩的坑:GmSSL默认只编译了标准算法核心,部分国密相关模块默认并不启用。我这次编译时需要SM2、SM3、SM4全量支持,需要在CMake配置时显式打开相关选项。
GmSSL的CMakeLists里常用的开关有:
-DGMSSL_BUILD_BIN=ON:是否编译命令行工具,默认ON,不需要可以关掉省时间。-DGMSSL_BUILD_TESTS=OFF:关闭测试编译,可以大幅缩短编译时间。-DGMSSL_BUILD_EXAMPLES=OFF:关闭示例程序。
等完成一次基础编译后,可以用自带的命令行工具验证国密算法是否正常。比如编译生成的文件里有gmssl命令行程序,执行:
bash复制gmssl sm3 -in test.txt
如果正常输出一串64位的十六进制摘要,说明SM3算法模块工作正常。SM2、SM4也可以用它进行加解密和签名验签测试。
注意:如果你在CMake配置时发现某些国密模块的宏未定义,检查一下GmSSL源码include目录下的版本配置头文件。这些模块的开关很多是编译时由CMake写入配置文件的,不能手动改源码,否则后续编译会冲突。
3.4 MSVC产物的使用方法:在VS工程里链接GmSSL
编译完成后,在Visual Studio工程里使用GmSSL的方法很简单。右键工程属性,在VC++目录里:
- “包含目录”添加:
D:\libs\gmssl-msvc\include - “库目录”添加:
D:\libs\gmssl-msvc\lib - “链接器 -> 输入 -> 附加依赖项”添加:
GmSSL.lib
如果你是直接从CMake调用,则用:
cmake复制find_package(GmSSL REQUIRED)
target_link_libraries(your_target PRIVATE GmSSL::SSL GmSSL::Crypto)
需要提醒一下,GmSSL的头文件是C接口,在C++工程里引用时务必包一层extern "C",否则C++名字修饰会让链接器找不到符号:
cpp复制extern "C" {
#include <gmssl/sm2.h>
#include <gmssl/sm3.h>
#include <gmssl/sm4.h>
}
4. MinGW编译GmSSL的完整流程
4.1 MinGW-w64的版本选择:直接上MSYS2或w64devkit
MinGW的发行版比较多,老旧的MinGW.org版本不建议用了,推荐MinGW-w64。有两个选择路径:
- 安装MSYS2,用pacman装mingw-w64-x86_64-gcc工具链。这是最规范的推荐方式。
- 直接用w64devkit这个便携式开发环境,解压即用,自带GCC、Make、NASM,无需安装。
我建议分场景考虑:临时测一下,想快速看到结果,用w64devkit,下载解压完设置PATH即可开始编译,免安装,也不污染系统。但如果你以后还想在这个环境里编其他库,比如开源项目常用的libsodium、cJSON、sqlite3等,MSYS2会更方便,因为它的包管理器能直接装好所有依赖头文件。
这里就以MSYS2为例说明。安装完MSYS2,打开“MSYS2 MinGW x64”终端,先更新并安装工具链和依赖:
bash复制pacman -Syu
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-nasm mingw-w64-x86_64-perl
注意一定是MSYS2 MinGW x64终端,不要用MSYS2 MSYS终端——那里的环境变量和工具链是MSYS风格,编译出来的东西跟Windows原生程序有些差异。
4.2 MinGW编译命令:Makefile与CMake的逐一执行
在源码根目录建立build-mingw目录,通过MSYS2 MinGW x64终端运行:
bash复制mkdir build-mingw
cd build-mingw
cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=D:/libs/gmssl-mingw -DBUILD_SHARED_LIBS=OFF
mingw32-make -j8
mingw32-make install
第一行里的-G "MinGW Makefiles"是重点。CMake如果不指定生成器,在MSYS2 minGW终端里有可能默认到老式Makefiles,配合微软NMAKE会不对路。显式指定MinGW Makefiles后,CMake会生成Makefile,并用mingw32-make执行。
-DCMAKE_BUILD_TYPE=Release这个参数在VS生成器下不需要,但在MinGW生成器下必须设,否则默认是空,编出来的是无优化调试版。
如果编译过程中报perl不识别之类的问题,检查一下PATH里是否把MSYS2的/usr/bin排在了MinGW相关路径之前,这可能导致命令解析异常。我实际遇到的情况是perl版本不对——MSYS2自带的perl跟GmSSL的Configure脚本兼容性差,建议用Strawberry Perl并把其路径置前。
4.3 静态编译MinGW版本的关键:避免运行时DLL依赖
MinGW编译出来的程序有一个特点——默认情况下动态链接了libgcc_s_seh-1.dll、libwinpthread-1.dll等运行时库。当你把编好的exe拷贝到其他机器上运行时,会提示缺少DLL,特别烦人。
对于GmSSL库本身,这个依赖会传导到调用方。我们做静态编译时,要么完全静态把所有运行时库链入,要么在调用方程序里处理依赖。建议编译时加上静态链接标志,在CMake里设置:
bash复制cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=D:/libs/gmssl-mingw -DBUILD_SHARED_LIBS=OFF -DCMAKE_EXE_LINKER_FLAGS="-static -static-libgcc -static-libstdc++"
-static把libgcc和libwinpthread也静态链入。这样产出的库和调用方exe在部署时不会莫名其妙地缺DLL。
提示:GmSSL是纯C库,理论上不需要libstdc++,但加上也无妨,防止以后第三方代码里有C++部分。
5. 编译中常见的报错与排查技巧实录
5.1 汇编编译失败:“no such instruction”与NASM缺失
这是最典型的报错之一,具体表现为编译过程中突然跳出类似:
code复制xxx.s: Assembler messages:
xxx.s: Error: no such instruction: `vpshufb'
或者提示无法打开nasm。
原因基本就两个:NASM没装,或者装了但编译器因为找不到nasm而走了一条奇怪的汇编路径。GmSSL的某些国密算法实现里有AVX/AVX2指令集的汇编优化代码,这些代码需要nasm编译成目标文件,再与C代码链接。当你没装NASM,或者NASM版本过低,就会在汇编阶段直接报错。
解决方式:确认NASM已安装并在PATH中;在MSVC命令行窗口里执行nasm -v验证;如果GmSSL默认开启了汇编优化,而你的实际目标平台不支持,可以在CMake配置时关闭汇编优化。但通常不建议关——SM4的汇编实现和C实现性能差距有5到10倍。
5.2 Perl相关报错:Configure脚本无法执行
用Configure脚本方式编译时,报错信息通常是:
code复制Can't locate Config.pm in @INC
或者提示找不到perl。这类问题多半是Perl环境不干净,或ASLR/路径问题导致Perl模块加载失败。
我在Windows上推荐直接改用CMake方式编译,因为GmSSL的CMakeLists不需要Perl。如果你坚持用Configure脚本方式,那就得确保Perl版本大于5.20,且必须用ActivePerl(社区版已经不能免费用了)或Strawberry Perl。
5.3 x64和x86架构不匹配:AMBIGUOUS ARCHITECTURE
这个错误出现在你同时装了32位和64位依赖的时候。具体报错类似:
code复制fatal error LNK1112: module machine type 'x86' conflicts with target machine type 'x64'
出现这个错误,九成是你用64位编译器去链接32位库,或者反过来。解决方式:检查CMake缓存中CMAKE_GENERATOR_PLATFORM是否为x64;检查所链接的依赖库到底是哪个架构编出来的;用VS的x64 Native Tools终端重新配置CMake。
我踩过一次:GmSSL源码里不过是没有清理旧的build目录,里面残留了上次32位编译的CMakeCache.txt,CMake会沿用旧配置,导致后面所有编译都在错误的架构里打转。删除build缓存目录,重新cmake,问题就消失了。
5.4 MinGW链接MSVC库文件:LINK : fatal error LNK1104
在MinGW环境下链接MSVC编出来的.lib,通常直接报fatal error LNK1104(无法打开文件)。反之,MSVC链接MinGW的.a也经常报unresolved external symbol。
这本质上不是“文件缺失”,而是格式不兼容。MSVC的.lib是COFF,MinGW也认COFF,但导入库的符号命名和重定位格式有差异。MinGW项目的正确做法是,如果程序已经用了MinGW,GmSSL必须也用MinGW从源码编译,不要试图直接拿MSVC的库文件链接。
5.5 编译通过但运行时提示找不到DLL
如果你编译的是动态库版本(BUILD_SHARED_LIBS=ON),编译和链接都通过了,但运行自己的程序时却报错“找不到gmssl.dll”或“找不到libgmssl.dll”。
原因很简单:Windows运行时只在exe所在目录和系统PATH里查找DLL。GmSSL编译出来的DLL在install目录里,你的exe不在同一目录,自然找不到。
解决方式:把DLL复制到exe同级目录;或者把install目录的bin路径加进系统PATH。
5.6 编译期异常:内存不足与超大编译任务
GmSSL全量编译消耗的资源不算大,但如果用--parallel 8依然报内存不足,一般是编译过多模块导致的。可以关掉不必要的测试、示例和文档:
bash复制cmake .. -DBUILD_TESTING=OFF -DGMSSL_BUILD_EXAMPLES=OFF
或者把并行数降低到4。另外保证编译时磁盘至少有5GB可用空间,编译中间文件有时比最终库大得多。
6. 实测对比:MSVC和MinGW两种产物的差异与选择建议
6.1 静态库VS动态库:体积、部署、依赖对比
为了直观展示,我把两种编译方式的结果整理成了一个对比表:
| 对比项 | MSVC编译 | MinGW编译 |
|---|---|---|
| 静态库文件名 | GmSSL.lib | libGmSSL.a 或 GmSSL.a |
| 动态库文件名 | gmssl.dll + GmSSL.lib(导入库) | gmssl.dll + libgmssl.dll.a(导入库) |
| 运行时依赖 | UCRT(Windows10+自带),部署简单 | 需静态链接libgcc/libwinpthread,或带上DLL |
| 链接兼容性 | 仅MSVC可链接 | 仅MinGW/GCC可链接 |
| 调试支持 | PDB调试信息,VS可视化调试 | GDB调试,支持较一般 |
| 部署体积 | 静态库较瘦,依赖少 | 静态库较大,但部署省心 |
| 适用场景 | VS原生Windows开发 | Qt MinGW开发、开源工具链 |
6.2 编译器版本与性能差异
还有一点值得注意,MSVC和MinGW编译出的代码在性能上有时会有差异。我简单用SM3算法做了个基准测试,同一台机器上,MSVC Release版本每分钟处理的哈希块数略高于MinGW默认参数版本,差距大概在5%-10%。但MinGW加-O2优化后差距基本消失。所以如果你对性能有极致要求,建议在MinGW编译时手动加优化参数:
bash复制-DCMAKE_C_FLAGS="-O2 -march=x86-64"
这个不加也不影响正确性,只是纯粹的性能调优。
6.3 项目实际选型建议
我这次做的是国密工具组件,最终选的是MSVC静态库,因为主程序是VS开发的,集成的其他库也全是MSVC编译,统一工具链最安全。MinGW版本编出来主要用于交叉验证算法的一致性和兼容性。实际跑了两套,SM2加解密、SM3摘要和SM4加解密结果完全一致,说明核心算法实现是标准的。
如果是为Qt MinGW项目准备GmSSL,那直接用MSYS2的MinGW-w64工具链编译就好,稳定性和兼容性都验证过。如果你是一个跨平台开源库的维护者,我建议两套通吃,CI里可以同时跑VS和MinGW构建,毕竟是国密库,多一重验证总比少一重好。
另外,GmSSL的版本更新比较活跃。我编译的基于较新的release分支,如果你的项目用了老版本,编译流程可能有些微差异,但整体思路一致——核心就是工具链选择、CMake配置、编译命令三件事。
最后再分享一个小经验:不管用哪套编译器,编译完成后第一时间用官方提供的命令行工具做个自测,用sm2生成一对密钥,然后用sm2签名、验证签名,再用sm4做一轮加解密,确认算法链路是通的再集成到业务代码里。别等到程序跑起来了才发现库编得有问题,那时候排查成本就完全不可控了。
整个项目编译下来我的体会是,GmSSL的跨编译器和跨平台能力做得比想象中好,大部分问题都出在环境不一致和工具链匹配上。只要把编译器选型和依赖库配置得当,MSVC和MinGW两条路都能走通,而且跑出来的算法结果完全一致。
