资讯详情

Windows下OpenSSL 1.1.1c编译接入与LNK2019排查

📅 2026/10/28 22:41:55 | 华诺云谱 👁 阅读
Windows下OpenSSL 1.1.1c编译接入与LNK2019排查
简介面向需要在 Windows 平台集成 SSL/TLS 能力的 C/C 开发者提供 OpenSSL 1.1.1c 经 Visual Studio 2015 编译后的静态库与动态库成品覆盖 32 位和 64 位可作为免去从源码搭建编译环境的现成依赖。压缩包共 928 个文件约 43.73MB以 848 个 h 头文件为主配套 16 个 lib 静态导入库、16 个 dll 动态库、28 个 pdb 调试符号以及少量 exe、pl 与 c 源文件便于按位数和链接方式取用。已有 1636 人学习下载说明其在 Windows 下配置 OpenSSL 开发环境时具有一定参考价值。使用者可将头文件与对应 lib/dll 直接接入工程结合 pdb 辅助调试按静态或动态链接需求选择 32/64 位版本减少自行编译与排错成本。1. 版本没对齐一个下午就没了接手一个老项目时仓库的 third_party 目录里安静地躺着一份 OpenSSL_1_1_1c 的编译文件两个 dll、两个 lib、一整个 include 目录。看着挺齐全我把包含路径和库路径配上去编译一次通过链接阶段报出四十多个 LNK2019。查了很久才弄明白工程里另一处附加包含目录指向了旧版本目录头文件按 1.0.x 解析lib 按 1.1.1 链接符号名根本对不上。这种情况在 openssl 上特别常见。1.1.x 与 1.0.x 之间 API 断代很彻底连导入库文件名都换了很多人照着几年前的帖子配置第一步就走偏。用这套编译文件的人通常分两类一类直接拿来接进工程一类要自己从源码走一遍 Configure 复现。无论哪条路位数、版本、运行时这三件事必须一次对齐否则后面遇到证书、握手、DLL 加载的毛病排查起来就是纯粹的黑匣子。下面按我实际走过一遍的顺序写从环境清单到编译命令再到接进 VS 工程和出问题怎么救。2. 环境先摆清工具链、目录和那份编译文件到底包含什么动手之前先把清单列完整能省掉后面大量返工。Windows 上编译 openssl 不算复杂但它对工具链版本和目标平台非常敏感任何一项不匹配报错信息都不会直接告诉你根因。我一般先确认三件事有没有 C 编译器环境、有没有 Perl、有没有 NASM然后再去看目录里那份编译文件是 x86 还是 x64。2.1 编译器、Perl、NASM 三件套的版本取舍编译 OpenSSL_1_1_1c 用的是 Visual Studio 自带的 nmake 和 cl不需要 CMake。社区里用 VS 2017 和 VS 2019 的居多1.1.1c 这个年代的版本在这两套工具链上跑通率最高VS 2022 大体也能编但如果手上是十年以上的老工程尽量跟工程本身的工具集保持一致别一边用 v142 编库、一边用 v143 编主程序。Perl 是硬性依赖Configure 脚本本身就是 Perl 写的。Strawberry Perl 的免安装包最省事解压后把 bin 加进 PATH 即可装完必须能在命令行里跑perl -v。ActivePerl 也能用但部分版本对反斜杠路径处理得比较别扭新手容易在这一步卡住。NASM 只在开启汇编优化时才需要64 位构建必须用 64 位的 nasm.exe32 位构建用 32 位的混用会直接编不过。我一般在调试阶段统一加no-asm把汇编关掉减少一个变量等功能跑通了再换成带汇编的产物。组件建议版本作用选错的典型后果Visual Studio2017 / 2019v141 或 v142 工具集提供 cl、link、nmake工具集和主工程不一致CRT 版本打架Perl5.20 以上Strawberry 免安装版执行 Configure 脚本版本过低时 Configure 直接报语法错误NASM2.13 以上位数与目标一致汇编优化位数不匹配时编译中断报汇编器错误目标平台VC-WIN64A 或 VC-WIN32决定产物位数与主工程位数不一致链接报 LNK1112验证工具是否就位三条命令就够perl -v nasm -v cl前两条任意目录都能跑第三条只有在配好 VC 环境的命令行里才有效。如果cl提示不是内部或外部命令说明环境没进对先别急着 Configure。提示三条命令的输出里要重点看位数信息。perl -v会打印是否 64 位nasm -v会打印编译目标cl开头那行会写明 x86 还是 x64。三者一致后面的坑能少一半。2.2 目录布局源码、安装产物和压缩包分开放OpenSSL 1.1.1 是原地构建编译中间文件会直接生成在源码目录里这一点跟很多 CMake 工程不一样。所以源码目录不要随手解压到桌面也不要跟其他项目的依赖混在一起否则清理的时候很难受。我习惯这么分D:\dev\openssl\ ├─ downloads\ 原始压缩包留档 ├─ src\openssl-1.1.1c\ 解压后的源码同时是构建目录 └─ dist\x64\ --prefix 指向的安装目录 ├─ bin\ dll 与 openssl.exe ├─ lib\ 导入库 ├─ include\openssl\ 头文件 └─ ssl\ openssl.cnf 与证书搜索目录两个硬性要求整条路径不能有空格不能有中文。Configure 生成的 Makefile 里会把这些路径原样拼进去带空格的路径在某些规则下会被截断报出来的错还跟你以为是两回事。D 盘根目录下建一个纯英文短路径是最省心的做法比如D:\dev\openssl。同时把 32 位和 64 位分两个 dist 子目录存dist\x64和dist\x86。工程里切换平台配置的时候只改包含目录和库目录两套产物互不干扰也不会出现拿错 lib 的情况。2.3 打开正确的命令行环境这一步看着简单实际上翻车率不低。开始菜单里搜 “x64 Native Tools Command Prompt”选对应 VS 版本的那个双击进去即可。如果习惯从普通 cmd 里启动就手动调 vcvars64.batcall C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat where cl where nmake路径里的 VS 版本号和安装类型Community / Professional按自己机器改。路径本身带括号和空格所以必须用双引号包住否则 cmd 会从Program那里截断。不确定装在哪可以用 vswhere 查一下安装路径再拼。进去之后确认两件事where cl输出的是你期望的 VS 目录下的 cl.exeset VSCMD_ARG_TGT_ARCH输出 x64。如果这里显示 x86说明进的是 32 位环境后面就算 Configure 写了 VC-WIN64A 也会出问题。还有一点容易被忽略这个命令行窗口是环境变量的载体nmake 必须在同一个窗口里跑。中途关掉重开就得重新 call 一遍 vcvars别在没配环境的窗口里直接敲 nmake那样报的错全是误导性的。3. 编译 OpenSSL_1_1_1c从 Configure 到 nmake install环境齐了就可以动手。整个流程四步Configure 生成 Makefile、nmake 编译、nmake test 自测、nmake install 装到前缀目录。真正需要思考的只有第一步的参数后面三步基本是等。3.1 Configure 参数逐项过一个字符决定产物形态先切到源码目录再执行 Configure。下面这行是我在调试阶段的标准写法cd /d D:\dev\openssl\src\openssl-1.1.1c perl Configure VC-WIN64A no-asm no-shared --prefixD:\dev\openssl\dist\x64 --openssldirD:\dev\openssl\dist\x64\ssl逐项拆开看。VC-WIN64A是目标平台标识对应 x6432 位用VC-WIN32。这个参数写错是最高频的事故来源因为 Configure 不会拦你它会老老实实把 32 位产物编出来直到你在 VS 里链接时才看到 LNK1112 模块计算机类型冲突。no-asm表示不启用汇编优化代价是加解密性能略降好处是不依赖 NASM编译过程干净。调试阶段我用它正式出包时去掉让汇编模块参与编译。no-shared表示只生成静态库、不生成 dll。这个参数决定了你最终拿到的是两个 .lib 还是 .lib 加 .dll 的组合。如果工程需要动态加载或者多模块共享同一份加密实现去掉no-shared产物里就会多出libcrypto-1_1-x64.dll和libssl-1_1-x64.dll。--prefix是 install 的目标目录也就是前面规划的dist\x64。--openssldir单独指向配置文件目录用来放 openssl.cnf 和证书搜索路径。这个参数强烈建议显式写因为它的默认值在 Windows 上会解析到当前盘符根目录下的\usr\local\ssl一个你根本不会想到去看的地方后面程序找不到配置就报得莫名其妙。还有几个业务场景会用到的变体可以按需组合参数含义典型使用场景no-shared只出静态库单文件分发不希望带 dllshared出 dll 加导入库多个模块共用一份实现no-asm关汇编调试阶段、没装 NASM--debug带调试符号需要跟进 openssl 内部崩溃-MT / -MD指定 CRT 链接方式与主工程的运行库设置对齐zlib启用 zlib 压缩需要 TLS 压缩多数场景不用这里有个与老教程的差异必须说明1.0.x 时代那套先跑ms\do_ms.bat或ms\do_win64a.bat、再 nmake -f ms\ntdll.mak 的流程在 1.1.x 里已经废弃。1.1.1 直接用perl Configure生成顶层 Makefile再去翻老帖子照抄会找不到文件。这一点我看过太多人栽在上面。Configure 跑完正常情况下会打印一串 “Configuring for VC-WIN64A” 之类的内容最后落一个 Makefile 在当前目录。如果这一步报错基本集中在两个原因Perl 不在 PATH 里或者路径里有空格。3.2 nmake 与 nmake test中间别乱动目录Configure 成功之后依次三条命令nmake nmake test nmake install第一条编译耗时跟机器性能有关十几分钟到半小时都正常。过程中会刷大量 cl 命令行只要没出现 error让它跑完就行。中途不要在这个目录里开别的进程写文件也不要去改源码Makefile 的依赖判断没那么聪明。第二条是自带测试套件会跑几百个用例覆盖加解密、证书解析、握手流程。这一步值得花时间等因为它是唯一能在接入工程之前暴露工具链问题的手段。如果测试里有几条失败先看失败用例名跟平台相关的通常是时间戳、随机源、文件权限这几类可复现的失败才需要处理。第三条把产物装到--prefix指定的目录。install 阶段报错一般是权限问题路径指向了 Program Files 之类的受保护目录。这也是我前面坚持装到 D 盘自建目录的原因之一。如果 nmake 中途失败别急着改配置重来先执行一次清理nmake clean大改配置比如从 shared 换成 no-shared之前用nmake distclean把 Makefile 一起删掉再重新 Configure。残留的中间文件导致下一次构建失败这类问题非常隐蔽你以为改了参数其实用的还是上一轮的东西。3.3 install 之后的文件清单安装完去dist\x64底下核对目录结构。共享库构建的典型布局是这样dist\x64\ ├─ bin\ │ ├─ libcrypto-1_1-x64.dll │ ├─ libssl-1_1-x64.dll │ └─ openssl.exe ├─ lib\ │ ├─ libcrypto.lib │ ├─ libssl.lib │ └─ engines-1_1\ ├─ include\openssl\ .h 头文件 └─ ssl\ openssl.cnf静态构建时 bin 下只有 openssl.exelib 下多出两个体量很大的静态库文件。dll 名字里的-x64后缀是 1.1.x 的命名规则32 位产物没有这个后缀叫libcrypto-1_1.dll。这个后缀差异正好可以用来快速判断手上这份编译文件到底是什么位数比翻文档快。openssl.exe别丢后面验证环节全靠它。有些人只把 dll 和 lib 拷走把 exe 留在原目录等到要排查握手问题的时候又得回头找。4. 接进自己的工程头文件、库目录和 DLL 落地库编出来只是半成品能不能在工程里跑起来取决于三处配置有没有对齐包含目录、库目录加附加依赖项、运行时 dll 的位置。这三处任何一处出问题症状都不一样正好可以反推。4.1 附加包含目录与库目录的顺序问题在 VS 里打开项目属性确认当前选的是 x64 平台配置然后改两处C/C → 常规 → 附加包含目录D:\dev\openssl\dist\x64\include链接器 → 常规 → 附加库目录D:\dev\openssl\dist\x64\lib这里有个顺序陷阱。VS 的包含目录是拼接搜索的如果工程里还有从父级继承来的旧路径或者某个第三方目录里也塞了一份 openssl 头文件编译器可能先命中旧的。表现出来就是编译能过、链接报一堆找不到符号或者反过来编译期就报某个宏未定义。排查办法是在项目属性里把“附加包含目录”的实际展开值看一眼确认D:\dev\openssl\dist\x64\include排在前面。我自己踩过一次解决方案级属性里配了一份 OpenSSL 路径项目级属性里又配了一份旧的项目级覆盖解决方案级结果一直用的是旧头文件。查了半天才想到去看属性继承。从那以后我习惯在项目里只保留一处配置不搞两层。如果工程是多配置的Debug 和 Release 要分别确认一遍。VS 默认只改当前选中配置切过去发现路径没生效的情况很常见。4.2 链接静态库还是导入库libcrypto 与 libssl 的关系附加依赖项里加两个名字。用导入库共享构建时是// 链接器 → 输入 → 附加依赖项里填 // libssl.lib // libcrypto.lib // // 或者在源码里直接指定省得每个工程配一遍 #pragma comment(lib, libssl.lib) #pragma comment(lib, libcrypto.lib)顺序上 libssl 依赖 libcrypto把它放前面。MSVC 链接器有一定的符号重扫能力但依赖关系写反了在增量构建里偶尔会抽风养成习惯更稳。1.1.x 的导入库名字就是libcrypto.lib和libssl.lib跟静态库同名。这一点跟 1.0.x 的libeay32.lib、ssleay32.lib完全不同照老教程写必然链接失败。判断手上的库是静态还是导入看文件体积最直接导入库通常几十 KB 到一两百 KB静态库动辄几 MB。如果选了静态链接no-shared编出来的除了加这两个 lib还要额外链接系统库ws2_32.lib和crypt32.lib。少了这两个会报一堆__imp_socket、__imp_CertOpenStore之类的未解析符号看着完全不像跟 openssl 有关。CRT 设置必须对齐。OpenSSL 默认共享构建走/MD静态构建走/MT的倾向更明显。你的主工程用哪个库就得用哪个。混用的时候链接可能不报错运行时在内存分配和释放的边界上崩溃属于最难查的一类问题。稳妥做法是显式在 Configure 那行加上-MD或-MT跟工程设置强行拉齐。初始化方面1.1.x 之后不需要手动调用SSL_library_init()和SSL_load_error_strings()框架会自动初始化。保留这些调用不会报错但会给后来接手的人造成误解以为还得手动管。要用新写法就用OPENSSL_init_ssl(0, nullptr)。4.3 DLL 放哪运行时搜索路径的三个位置共享构建的产物编译链接都对程序一跑起来弹“找不到 libcrypto-1_1-x64.dll”这是新手最容易撞的一堵墙。Windows 加载 dll 的顺序大致是exe 所在目录 → 系统目录 → PATH 环境变量里的目录。所以正确的落点只有三个一是复制到 exe 输出目录最省事也最不容易出问题。用生成后事件自动做xcopy /Y /D $(SolutionDir)third_party\openssl\bin\*.dll $(OutDir)/D表示只复制源文件比目标新的增量构建时不会每次都覆盖速度更快。二是把dist\x64\bin加进 PATH。适合本机开发调试不适合分发因为换台机器就失效了。三是丢进系统目录。不要这么干。系统目录里已经有一份别的程序放进去的旧版 openssl dll 时加载顺序会让你的程序用上错误的版本而且症状完全随机今天好好的明天崩。顺带说一句如果机器上同时存在 32 位和 64 位的 openssl dll别把两个 bin 目录都塞进 PATH。位数不匹配时加载会失败报的还是“找不到模块”容易往路径配置上想。5. 避坑与排查五条我真实踩过的记录下面这五条按现象、原因、解决的顺序写。前三条发生在编译链接阶段后两条发生在运行和握手阶段。每条我都尽量写清楚该看哪儿而不是只给结论。5.1 编译与链接阶段的翻车记录第一条链接报几十个 LNK2019符号全是SSL_CTX_new、SSL_new这类。原因是头文件和库版本不一致或者干脆是 1.0.x 的头文件配了 1.1.1 的 lib。1.1.x 里大量结构体变成了不透明类型函数签名也变了符号名自然对不上。解决方式是先定位到底用的哪份头文件。在源码里打印一下版本宏#include openssl/opensslv.h #include stdio.h int main(void) { // 1.1.1c 的版本号是 0x1010103fL对不上就说明头文件找错了 printf(0x%08lx\n, OPENSSL_VERSION_NUMBER); return 0; }打印出来的值和预期不符就回到项目属性里清理包含目录把旧路径彻底删掉而不是靠调整顺序去“压过”它。第二条LNK1112模块计算机类型 x64 与目标计算机类型 x86 冲突。原因是 Configure 时目标平台写错了编出 32 位产物拿去给 64 位工程用或者反过来。这个错误信息其实很明确但很多人第一反应是去改工程的平台配置越改越乱。解决办法是确认主工程的平台回头nmake distclean清干净用正确的 VC-WIN64A 或 VC-WIN32 重新 Configure 一遍。同时检查 NASM 位数64 位目标配 32 位汇编器一样会出问题。第三条nmake 跑到一半报 U1077 或 U1081。原因通常两个一是当前命令行没进 VC 环境nmake 找不到 cl二是上一轮构建残留了半成品依赖关系错乱。先where cl确认环境再nmake clean清一遍。如果是换了配置参数之后出的错直接nmake distclean把 Makefile 一起删掉重来比一点点排查快得多。5.2 运行与握手阶段的翻车记录第四条程序启动就弹窗说找不到libcrypto-1_1-x64.dll。原因就是 4.3 里说的dll 不在搜索路径上。开发机上最容易出现的变体是调试时能跑直接双击 exe 就报错因为 VS 调试器会把库目录临时加进搜索路径。解决方式是统一用生成后事件把 dll 拷到输出目录别依赖调试器的行为。分发之前用 dumpbin 检查一下 exe 的依赖列表确认引用的 dll 名字和实际带过去的文件完全一致包括那个-x64后缀。第五条TLS 握手报certificate verify failed但对端证书明明是好的。原因是 1.1.1 在 Windows 上没有内置的默认 CA 路径SSL_CTX_set_default_verify_paths()找不到任何根证书验证链自然是空的。解决方式是显式加载一份 PEM 格式的根证书包SSL_CTX *ctx SSL_CTX_new(TLS_client_method()); // 指向你自己的 ca-bundle.pem路径用绝对路径更稳 if (SSL_CTX_load_verify_locations(ctx, D:\\dev\\openssl\\certs\\ca-bundle.pem, nullptr) ! 1) { ERR_print_errors_fp(stderr); }排查这类问题时把SSL_get_verify_result()的返回值打出来配合ERR_print_errors_fp看错误队列比盯着日志猜要快得多。openssl 的错误队列是个宝库很多人只打印一句“握手失败”就卡住了。6. 装完怎么验证一次本地自握手加二十行代码库编完、工程配完还不能算装好。真正能让人放心的验证是两层先用 openssl.exe 跑一次完整的 TLS 握手证明库本身没问题再用二十行代码调一次 API证明你的工程配置没问题。两层都过了才算这套环境能交付。6.1 用 openssl.exe 自己连自己先确认版本和构建参数cd /d D:\dev\openssl\dist\x64\bin openssl version -a输出里会列出编译器、目标平台、安装目录。重点核对platform是不是 VC-WIN64AOPENSSLDIR是不是你--openssldir里写的那个路径。这两项错了说明前面某一步有问题别往下走。然后生成一张自签名证书起一个本地服务端mkdir D:\tmp\ossltest openssl req -x509 -newkey rsa:2048 -nodes ^ -keyout D:\tmp\ossltest\srv.key -out D:\tmp\ossltest\srv.crt ^ -days 365 -subj /CN127.0.0.1 openssl s_server -accept 4433 -cert D:\tmp\ossltest\srv.crt -key D:\tmp\ossltest\srv.key -www-nodes表示私钥不加密省掉交互输入密码测试用足够。-subj里 CN 写 127.0.0.1跟后面客户端连的地址对上避免主机名校验失败干扰判断。这一串命令建议在 cmd 里跑PowerShell 对-subj里的斜杠处理不太一样容易出岔子。另开一个窗口用客户端连上去openssl s_client -connect 127.0.0.1:4433 -tls1_2 -CAfile D:\tmp\ossltest\srv.crt看重两点握手协商出的协议版本是不是 TLS 1.2最后那行Verify return code是不是 0。证书校验通过说明证书加载和验证链这条路径是通的。如果这里报错先把-CAfile换成-CApath或者临时加-verify_return_error看详细原因别急着去改代码。6.2 二十行代码验证 API 层真的能用命令行能跑不等于工程能跑最后一步是在你自己的 VS 工程里编一个小程序#include openssl/opensslv.h #include openssl/ssl.h #include openssl/err.h #include stdio.h int main(void) { // 打印运行期实际加载的库版本跟编译期宏对比 printf(runtime: %s\n, OpenSSL_version(OPENSSL_VERSION)); printf(compile: 0x%08lx\n, OPENSSL_VERSION_NUMBER); SSL_CTX *ctx SSL_CTX_new(TLS_client_method()); if (ctx nullptr) { ERR_print_errors_fp(stderr); return 1; } SSL_CTX_free(ctx); return 0; }代码不长但覆盖了三件事。第一头文件路径对了否则opensslv.h都找不到。第二库链接对了SSL_CTX_new和OpenSSL_version能解析。第三运行时 dll 加载对了程序能启动并打印出运行期版本号。TLS_client_method()是 1.1.x 的写法1.0.x 用的是SSLv23_client_method()。如果你的代码库里还有旧写法正好借这一步一并清掉。跑通之后对比两个版本号编译期宏和运行期字符串应当对应同一个版本。如果运行期打印出 1.1.1a 之类的东西说明 dll 加载到的是机器上另一份去查 PATH 或者 exe 目录里有没有残留的旧 dll。这个对比我几乎每次接入新机器都会做一遍因为它能在五分钟内把“看起来装好了”和“真的装好了”区分开。从那以后我每次装完 OpenSSL 都强制走一遍这三步openssl version -a看构建参数自签证书跑一次 s_client 看 verify code再编二十行代码看运行期版本号。三步都过才往工程里推中间任何一步跳过后面都要花十倍时间去找。希望这套流程能帮到你少走一段我走过的弯路。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑