ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Windows下自编译libcurl与OpenSSL:32/64位SDK构建全流程

Windows下自编译libcurl与OpenSSL:32/64位SDK构建全流程 简介面向Windows下C/C网络开发者的libcurl与OpenSSL开发库提供32位与64位两套实测可用的动态链接库和导入库适用于需要快速在项目中加入HTTPS请求、SSL/TLS加密通道、证书与密钥管理能力的桌面或客户端程序。在功能层面OpenSSL集成了常见密码算法、密钥和证书封装管理及SSL协议支持配合libcurl可以稳定完成加密网络通信从底层密码运算到上层请求处理均有现成接口。整套文件以RAR压缩包形式提供整体约15.83MB共3451个文件除20个dll和6个lib核心库外还包含166个头文件、大量HTML格式帮助文档以及少量调试符号和工具便于开发者快速查阅API、核对编译配置、定位运行故障。目前已有616人浏览学习适合Windows平台进行网络相关开发时选用或对比评估。资源在Windows 10环境下实测可用拿到后可直接配置开发环境借助示例源码和配置文件快速走通从编译链接到实际调用的全流程显著降低集成网络通信和密码学功能的门槛。 搞底层网络开发这么多年我越来越觉得有些基础件还是自己动手编译一份放在手边最踏实。比如 libcurl openssl 这套组合几乎是 C/C 做 HTTP/HTTPS 客户端绕不开的标配。之前有个项目需要同时给 32 位和 64 位的程序提供 SDK在网上下现成的库要么版本对不上要么依赖了一大堆运行库最后干脆自己把两套开发库完整编译出来。这篇文章就把整个过程中的方案选择、编译命令、集成方式、踩坑记录都整理一遍给正准备折腾这两个库的朋友做个参考。内容不涉及太高深的理论强实操照着做基本能跑通。适用对象很明确正在用 C/C 做桌面客户端、服务端插件或者嵌入式应用需要 HTTPS 请求能力又不想被第三方预编译库绑架的开发者。文章会覆盖从 OpenSSL 编译到 libcurl 编译再到工程里链接调用的完整链路同时把 32/64 位版本之间的细微差别和常见报错一并说清楚。1. 为什么要自己编译这套开发库1.1 现成库和自编译库之间的取舍网络下载一个 libcurl.dll 加一个 libssl.dll 确实方便但实际接入项目时会发现一堆问题。预编译库通常只提供特定编译器版本比如只支持 VS2015拿到 VS2022 工程里就链接报错、特定运行库模式/MT 和 /MD 混用直接崩溃而且你不知道它到底开了哪些协议、哪些 SSL 后端、是否包含 zlib、idn2 这些附加依赖。对发布型软件来说这些未知数都会变成上线前的定时炸弹。自己编译的好处在于可控。你可以只保留 HTTP/HTTPS 协议去掉 FTP、TFTP、SMB 等用不上的部分减少体积也可以决定是静态链接还是动态链接还能精准搭配 OpenSSL 的版本避免出现“libcurl 需要 OpenSSL 1.1.1但系统里装了 3.x”这种混乱局面。对于交付给客户的 SDK你甚至可以直接把头文件、库文件、许可证文件打包成一个干净的目录客户拿到手不需要额外装任何依赖。1.2 32 位和 64 位版本必须同时准备很多开发者的项目一开始是 Win32 平台后来客户要求支持 x64这时才发现库只有一份 32 位的底层全是坑。32 位和 64 位库不能混用这是常识但实际操作中经常有人把 x64 工程的库路径指到了 x86 目录然后出现一堆 LNK2019、LNK2001 错误查了半天才发现是路径问题。更麻烦的是有些老系统的第三方组件还是 32 位的比如某些银行插件、硬件加密锁驱动这时主程序虽然是 64 位却必须通过进程间通信去调 32 位模块两个模块各自需要一套 libcurl openssl。所以一份完整的 SDK 里必须同时包含 Win32 和 x64 两套产物include 头文件可以共用但 lib 和 dll 一定要分目录存放。我习惯的目录结构是这样的sdk/ include/ curl/ curl.h curlver.h ... openssl/ ssl.h crypto.h ... lib/ x86/ libcurl.lib libssl.lib libcrypto.lib libcurl.dll libssl-3.dll libcrypto-3.dll x64/ libcurl.lib libssl.lib libcrypto.lib libcurl.dll libssl-3.dll libcrypto-3.dll这样客户在 VS 里配置库目录时只需要根据目标平台选择 x86 或 x64完全不需要改额外配置。2. 编译前的准备版本、工具链和依赖2.1 OpenSSL 版本选择与 Perl 依赖OpenSSL 目前主流的有 1.1.1 系列和 3.x 系列。1.1.1 已经停止维护3.0 之后是 LTS3.2、3.3、3.6 等版本都是社区活跃版本。如果做商业产品建议优先选择 3.x LTS 版本安全更新周期更长。但注意OpenSSL 3.x 编译时对 Perl 的版本有要求而且启用了更多编译选项后还需要 NASM汇编加速。热搜词里的“perl is needed by openssl”就是第一次编译时最容易碰到的问题。在 Windows 上需要先安装 Strawberry Perl 或者 MSYS2 里的 Perl。Strawberry Perl 安装完成后会自动把 perl.exe 放进 PATH但如果你用的是 Visual Studio 的开发者命令行建议重新打开命令行窗口让环境变量生效。OpenSSL 编译还需要 NASM用于生成优化过的汇编代码。你可以从 NASM 官网下载安装包安装后确保 nasm.exe 在 PATH 中。编译前在命令行敲一下perl -v nasm -v两个命令都有输出说明依赖就绪。2.2 编译器工具链与“32 位还是 64 位”的第一个分岔口在 Windows 上编译最稳妥的方式是用 Visual Studio 的 MSVC 工具链。你不需要打开 IDE直接用“x86 Native Tools Command Prompt”和“x64 Native Tools Command Prompt”分别编译两套版本。注意不能用同一个命令行交叉编译因为 MSVC 的环境变量会限制目标架构。如果你更喜欢开源工具链也可以用 MinGW-w64。但 MinGW 编译出来的库在 MSVC 工程里链接时容易出现 CRT 冲突建议尽量保持“同工具链”原则要么全部用 MSVC要么全部用 MinGW。我实际项目中用的是 MSVC下面的命令也以 MSVC 环境为准。先分别打开两个命令行窗口确认 cl.exe 的版本信息cl能打印出 Microsoft C/C Optimizing Compiler 的版本号就说明环境没问题。接下来只需要在 x86 窗口里编译 x86 版本在 x64 窗口里编译 x64 版本步骤完全一样只是结果输出到不同目录。3. 编译全流程实操从 OpenSSL 到 libcurl3.1 编译 OpenSSL三条命令搞定下载 OpenSSL 源码并解压后进入源码根目录。以我的经历为例这里以 OpenSSL 3.x 的源码目录为例按顺序执行perl Configure VC-WIN32 --prefixC:\sdk\openssl\x86 --openssldirC:\sdk\openssl\x86\ssl nmake nmake install如果是 x64就把第一行换成perl Configure VC-WIN64A --prefixC:\sdk\openssl\x64 --openssldirC:\sdk\openssl\x64\ssl这里解释一下参数含义VC-WIN32表示使用 MSVC 编译 32 位版本VC-WIN64A表示 64 位版本--prefix是安装根目录编译完成后的 include、lib、bin 都会放到这个目录下--openssldir是 OpenSSL 运行时的配置文件路径一般放在安装目录里就行。如果不想编译汇编优化代码可以在 Configure 的时候加no-asm选项perl Configure VC-WIN32 no-asm --prefixC:\sdk\openssl\x86加了no-asm就不需要 NASM但性能会略差。对于一般客户端请求性能差别并不明显。如果要追求最快速度还是保留默认汇编选项。我建议保留默认因为 NASM 安装很容易没必要牺牲性能。编译过程中如果出现perl开头的报错多半是 Perl 环境变量没配好如果出现nasm相关的错误说明 NASM 没装或不在 PATH 里。这两点前面已经提过建议编译前都检查一遍。3.2 编译 libcurl带着 OpenSSL 头文件和库一起去libcurl 在 Windows 上编译的方式有很多种可以用 CMake也可以直接使用源码目录里的projects文件夹下的 VS 工程。我个人更推荐 CMake因为命令行可控性强而且方便生成静态库或动态库。假设你已经下载了 libcurl 源码解压到curl-src目录构建目录放在curl-build。这里以 CMake 为例先生成 x86 版本cmake -B curl-build-x86 -S curl-src -G Visual Studio 17 2022 -A Win32 -DCURL_USE_OPENSSLON -DOPENSSL_ROOT_DIRC:\sdk\openssl\x86 -DOPENSSL_INCLUDE_DIRC:\sdk\openssl\x86\include -DBUILD_CURL_EXEOFF -DBUILD_SHARED_LIBSON -DCMAKE_INSTALL_PREFIXC:\sdk\curl\x86 cmake --build curl-build-x86 --config Release --target install编译 x64 版本时把-A Win32改成-A x64同时把OPENSSL_ROOT_DIR和CMAKE_INSTALL_PREFIX改成 x64 对应的路径cmake -B curl-build-x64 -S curl-src -G Visual Studio 17 2022 -A x64 -DCURL_USE_OPENSSLON -DOPENSSL_ROOT_DIRC:\sdk\openssl\x64 -DOPENSSL_INCLUDE_DIRC:\sdk\openssl\x64\include -DBUILD_CURL_EXEOFF -DBUILD_SHARED_LIBSON -DCMAKE_INSTALL_PREFIXC:\sdk\curl\x64 cmake --build curl-build-x64 --config Release --target install这里我用了BUILD_SHARED_LIBSON也就是生成动态库。因为最终交付给客户的 SDK 里动态库可以减小客户主程序的体积也让升级 OpenSSL 时无需重新编译业务代码。如果你的产品更希望部署简单、不需要带一堆 DLL那可以改成OFF生成静态库。静态库链接时需要注意需要同时链接libcurl.lib、libssl.lib、libcrypto.lib并且要开启_WIN32_WINNT等宏。动态库的话工程只需要链接libcurl.lib导入库OpenSSL 的 DLL 会在运行时被加载。3.3 验证编译产物别急着集成编译安装完成后先检查目录结构是否完整。x86 和 x64 下都应该有include、lib、bin三个目录。用dumpbin工具可以查看导出符号确认库是 32 位还是 64 位dumpbin /headers C:\sdk\curl\x86\bin\libcurl.dll | findstr machine dumpbin /headers C:\sdk\curl\x64\bin\libcurl.dll | findstr machine前者应该输出x86后者输出x64。如果用dumpbin /dependents查看能看到 libcurl.dll 依赖了libssl-3-x64.dll等文件这表示 OpenSSL 是以动态库方式链接进来的。需要特别确认的还有include目录里的curlver.h和 OpenSSL 的opensslv.h看看版本号是否和预期一致。经常有编译完发现curl/curl.h版本已经更新到 8.x但 OpenSSL 头文件是旧版而 DLL 是新版这种不一致在运行时最容易出问题。4. 在工程里安全地集成开发库4.1 目录配置与运行库模式匹配以 Visual Studio 为例在工程属性里配置“C/C” - “常规” - “附加包含目录”加入C:\sdk\curl\x64\include按目标平台分别选 x64 或 x86。“链接器” - “常规” - “附加库目录”加入C:\sdk\curl\x64\lib。“链接器” - “输入” - “附加依赖项”填写libcurl.lib; libssl.lib; libcrypto.lib; ws2_32.lib; crypt32.lib; normaliz.lib。这里的ws2_32.lib和crypt32.lib是 Windows 系统库libcurl 和 OpenSSL 会用到normaliz.lib是国际化域名相关如果没有启用 IDN 可以不加但加上更保险。有一个特别容易踩的坑运行库模式。编译出的 OpenSSL 和 libcurl 如果是/MD动态 CRT那么你的工程也必须是/MD编译否则会出现内存分配/释放跨模块边界的问题典型表现是程序启动崩溃或者随机内存错误。建议在编译库的时候就用/MD这样客户工程默认是/MD的 Release 配置直接匹配。4.2 一个最小可用的 HTTPS 客户端示例写一个简单的 C 程序用 libcurl 访问 HTTPS 接口验证开发库能正常工作。代码不长直接贴出来#include stdio.h #include curl/curl.h static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) { fwrite(ptr, size, nmemb, (FILE *)userdata); return size * nmemb; } int main(void) { CURL *curl curl_easy_init(); if (!curl) { fprintf(stderr, curl_easy_init failed\n); return 1; } CURLcode res; curl_easy_setopt(curl, CURLOPT_URL, https://www.example.com/); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, stdout); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); res curl_easy_perform(curl); if (res ! CURLE_OK) { fprintf(stderr, curl_easy_perform() failed: %s\n, curl_easy_strerror(res)); } curl_easy_cleanup(curl); return res CURLE_OK ? 0 : 1; }这里注意CURLOPT_SSL_VERIFYPEER和CURLOPT_SSL_VERIFYHOST的设置。默认就是开启校验的但如果你没有配置 CA 证书路径libcurl 可能找不到证书而报错。生产环境建议显式指定 CA 证书curl_easy_setopt(curl, CURLOPT_CAINFO, C:\\sdk\\certs\\cacert.pem);如果没有专门下载 cacert.pem也可以在 OpenSSL 源码目录里找到apps/CA.pl生成的测试证书但那只适合开发不适合生产。更好的办法是从其他可信渠道获取最新的 CA 证书包。编译这段程序时上面提到的附加依赖项都要加上。编译成功运行后如果终端能输出www.example.com的 HTML 内容就说明整个链路打通了。5. 高频报错与排查实录5.1 OpenSSL 头文件版本和库版本不一致热搜词里有条 “checking openssl header version... 100020bf (openssl 1.0.2k 26 jan 2017)”这是典型的头文件和库版本不匹配。100020bf 这个十六进制表示的是 OpenSSL 1.0.2k 的版本号但你可能在链接 3.x 的库。这种问题多半是你系统里装过多个 OpenSSL 版本CMake 或 configure 脚本找到了旧版的头文件但链接器找到的是新版库。解决思路是彻底统一路径。在 CMake 命令行里显式指定OPENSSL_ROOT_DIR、OPENSSL_INCLUDE_DIR、OPENSSL_LIBRARY这几个变量不要依赖系统默认查找。另外在代码里打印一下 OpenSSL 版本号的预处理宏#include openssl/opensslv.h printf(OpenSSL header version: %s\n, OPENSSL_VERSION_TEXT);这样就能清楚地看到头文件版本。如果头文件版本和ssl.h对应的动态库版本不一致那就要回到编译环境检查路径。5.2 SSL read 错误error:0A000126“error:0A000126:ssl routines::unexpected eof while reading” 这个错误很多人遇到后第一反应是 OpenSSL 库编译错了其实大部分时候不是。这个错误表示 TLS 连接在读取过程中对端直接关闭了连接没有发完握手数据。最常见的原因包括服务端只支持 TLS 1.0/1.1而你用的 OpenSSL 3.x 默认禁用了这些旧协议或者服务端证书有问题或者网络中间层切断连接。排查方法先用命令行工具curl.exe去访问同样的地址加上-v参数看握手细节。我一般这样测试curl -v https://api.example.com/ --ssl-max-rfrc如果提示ALPN、TLSv1.3等正常那就是代码里的设置问题。如果确实是对端 TLS 版本过旧可以在 libcurl 里设置curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);不过这会降低安全性除非对方是老旧的嵌入式服务器否则建议还是让服务端升级。5.3 32/64 位库路径混乱导致 LNK2019项目里同时有 x86 和 x64 配置时VS 的中间目录和输出目录如果不区分会很混乱。我见过最经典的问题是x64 工程的附加库目录写死了 x86 的 lib 目录结果链接时一堆curl_easy_init等符号找不到。因为导入库不匹配符号名本身可能一样但机器码和调用约定不一致导致链接器直接报 LNK2019。解决办法有两个方向。一是严格使用 VS 的配置管理器为 Win32 和 x64 分别设置不同的附加库目录可以在属性页里选择“所有配置”但用$(Platform)宏区分C:\sdk\curl\$(Platform)\lib这样 x86 工程会解析成C:\sdk\curl\Win32\libx64 工程解析成C:\sdk\curl\x64\lib。前提是你目录名要和$(Platform)对应。如果不想改目录结构就老老实实在每个平台上单独指定路径。二是用dumpbin /headers检查目标 lib 库的机器类型确保和工程平台一致。命令行里输出machine (x86)还是machine (x64)一眼就能分辨。这两个方法配合起来基本能根治这类问题。5.4 DLL 运行时找不到依赖交付时如果使用动态库需要把libcurl.dll、libssl-3-x64.dll、libcrypto-3-x64.dll全部放到客户程序的 exe 同级目录或者系统 PATH 里。如果缺少某个 DLL程序启动时会直接弹错误框或者控制台应用毫无反应。排查这个问题可以用微软的 Dependencies 工具拖拽 exe 进去就能看到所有依赖的 DLL 以及缺失项。这个工具比老旧的 Dependency Walker 更新能正确识别编译后的导入表。6. 一点实战心得和技巧编译这套库这件事本身不难难的是保持一致性。我踩过不少坑之后形成了一套固定习惯分享一下第一每次编译前先确认命令行窗口的架构。x86 和 x64 的 Native Tools 窗口长得几乎一样很容易开错。我在标题栏会提前标注好窗口名字或者在编译输出后检查cl的输出信息。第二OpenSSL 的目录结构和 libcurl 的目录结构分开不要混在一个目录里。因为 OpenSSL 和 libcurl 的头文件同名情况不多但包含目录如果混在一起很容易出现 curl 包含到旧的 openssl 头文件的情况。第三批量编译时写一个脚本把 x86 和 x64 的构建过程串起来。下面是一个简单的批处理示意call C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat perl Configure VC-WIN32 --prefixC:\sdk\openssl\x86 nmake clean nmake install call C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat perl Configure VC-WIN64A --prefixC:\sdk\openssl\x64 nmake clean nmake install每次都要记得nmake clean否则这次编译的结果会残留在 obj 里和下一轮构建混在一起。同样的方式也可以继续封装 libcurl 的 CMake 构建最终一次性得到两套完整 SDK。第四关于 CA 证书。如果程序不需要严格校验证书开发阶段可以把CURLOPT_SSL_VERIFYPEER设为 0但发布版一定要保留默认校验。我会把最新的 cacert.pem 直接打包进 SDK并封装一个初始化函数设置CURLOPT_CAINFO这样业务方不需要知道证书存在哪。最后再分享一个小技巧可以用curl_version_info()函数在运行时打印 libcurl 和 OpenSSL 的版本信息方便技术支持远程排查。这个函数会返回一个结构体里面有ssl_version、libcurl_version字段可以在日志里直接输出。我见过的不少线上问题就是靠这一行日志快速定位到是库版本混用而不是业务代码的 bug。如果你也是从零开始给项目准备基础库希望这篇实操记录能帮你少走几步弯路。先把 OpenSSL 编译稳再把 libcurl 链接上最后在工程里跑通第一个 HTTPS 请求这套流程走一遍后后面再遇到类似的基础库编译需求基本就轻车熟路了。本文还有配套的精品资源点击获取
返回列表