资讯详情

资讯详情

VC2015编译libssh 0.10.3静态库:CMake配置与链接排错

简介面向 Windows 平台 C/C 开发者的 libssh-0.10.3 静态库由 VC2015vc140编译适合需要在自有程序中集成 SSH 远程登录、命令执行或安全文件传输能力的项目。包内提供 10 个文件以 7 个 h 头文件和 1 个 hpp 接口文件配合 2 个 lib 静态库为主含 debug 与 release 版本压缩包约 636KB便于直接加入 Visual Studio 工程并在链接阶段引用。已有 176 人学习下载。读者可据此省去从源码编译 libssh 的配置成本快速获得头文件、静态链接库及 C 封装入口用于搭建可调试的 SSH 客户端功能原型同时需留意 VC2015 运行时依赖与项目字符集、链接器设置适合具备一定 C/C 与 Visual Studio 使用经验的开发者参考。1. VC2015 编 libssh-0.10.3 静态库难的不是敲编译命令有个做跨平台客户端的同行把 libssh-0.10.3 源码拖进 VC2015 的解决方案目录右键生成三十多条 LNK2019 直接糊在输出窗口上。他以为是自己漏了什么源文件实际上是这套 libssh ssh.lib 静态库从源码到可用产物中间隔着一层很容易被忽略的依赖关系OpenSSL 的静态库、zlib 的静态库、运行时库的 /MT 与 /MD、LIBSSH_STATIC宏任何一环没对齐库能编出来但链不上。这套环境在 Windows 桌面客户端、内网运维工具、嵌入式网关的 PC 侧调试端里都很常见装不好最常见的表现是“编译通过、链接报错、运行崩溃”三段式翻车。这篇记录适合正在用 VC2015 或 v140 工具集接 SSH 能力、又不希望运行时拖一堆 dll 的开发者。2. 依赖先钉死VS2015、CMake 与 OpenSSL 静态库的版本账libssh 本身不重源码压缩包解出来也就几兆但它对下游依赖的版本和编译方式很挑。动手之前先把整条链上的版本写在一张纸上比装到一半回头查要省事得多。这套组合里真正决定成败的是三样东西VS2015 的 v140 工具集、能生成 VS2015 工程的 CMake、以及一份和最终 exe 使用同一套 CRT 的 OpenSSL 静态库。zlib 是可选依赖但只要你打算用压缩通道就得一并备齐并且同样要静态库版本。2.1 工具链版本对照生成器名字写错一步都走不动CMake 的生成器名字区分 32 位和 64 位写错不会报“你写错了”而是直接告诉你找不到编译器或者生成出一套 x86 工程然后在链接 64 位 OpenSSL 时给你一堆架构不匹配的错误。VC2015 对应的是 v140 工具集生成器字符串固定为Visual Studio 14 201532 位和Visual Studio 14 2015 Win6464 位。CMake 版本我一般钉在 3.2x 这一档太新的版本对 VS2015 工程格式的保留不如老版本稳没必要在这上面冒险。组件推荐版本说明Visual Studio2015v140安装时勾选“Visual C”与 Windows SDKCMake3.2x能生成Visual Studio 14 2015 Win64工程OpenSSL1.1.1 系列静态库与 VC2015 兼容需自行编译no-sharedzlib1.2.11 静态库产物名通常是zlibstatic.liblibssh0.10.3源码目录保持干净另建 build 目录检查工具链是否就位不用打开 IDE两条命令足够cmake --version cl 21 | findstr /i 19.00第一条打印 CMake 版本确认在 3.2x 区间第二条在 VS2015 的开发者命令提示符里执行19.00就是 v140 的编译器主版本号。很多人习惯在普通 cmd 里直接跑cl结果提示“不是内部或外部命令”然后误以为 VS 没装好其实是没进开发者命令提示符环境变量里的INCLUDE、LIB、PATH都没配。我一般直接在开始菜单里找“VS2015 x64 本机工具命令提示符”后面的所有命令都在这个窗口里敲省掉手配环境变量这一步。2.2 OpenSSL 与 zlib必须和 ssh.lib 用同一套 CRT这是整篇文章里最值得先讲明白的一点。VC2015 编译出来的静态库无论 libssh 还是 OpenSSL内部都带着对 C 运行时库的引用。VS 有两条路/MD走动态 CRT依赖msvcr140.dll/MT走静态 CRT把运行时代码打进产物。libssh 的静态库必须和 OpenSSL 静态库、zlib 静态库、最终调用它的 exe 保持同一条路否则链接期直接给你 LNK2038运行期则是更难查的堆损坏。OpenSSL 1.1.1 编静态库的常规做法perl Configure VC-WIN64A no-shared no-tests --prefixD:/deps/openssl-1.1.1w/x64 nmake nmake install_swVC-WIN64A指定 64 位目标32 位换成VC-WIN32no-shared只产出静态库避免后面 CMake 找错no-tests省掉测试程序的编译时间install_sw只装库和头文件不装文档。默认编出来的 OpenSSL 静态库是/MD的如果你的主工程是/MT需要在 Configure 阶段把CFLAG改成/MT或者编完之后用dumpbin检查一遍再决定。查 CRT 的办法很直接dumpbin /directives libcrypto.lib | findstr /i defaultlib输出里出现LIBCMT就是静态 CRT/MT出现MSVCRT就是动态 CRT/MD。这一步我建议对libcrypto.lib、libssl.lib、zlibstatic.lib各跑一遍三个结果必须一致后面配 libssh 时才有底气。zlib 用 CMake 编静态库关键是同样把 CRT 定死cmake -G Visual Studio 14 2015 Win64 ^ -DCMAKE_INSTALL_PREFIXD:/deps/zlib-1.2.11/x64 ^ -DCMAKE_C_FLAGS/MT ^ ../zlib-1.2.11 cmake --build . --config Release --target installCMAKE_C_FLAGS里塞/MT会作用到所有目标包括静态库目标CMAKE_INSTALL_PREFIX指向一个独立目录方便后面在 libssh 的配置里一次性指过去。编完检查D:/deps/zlib-1.2.11/x64/lib下应该有zlibstatic.lib如果只有zlib.lib和zlib1.dll说明静态库目标没编或者被覆盖了回头重来。记住这个文件名后面 CMake 找 zlib 时十个坑里有三个跟它有关。3. CMake 配置把 libssh-0.10.3 编成 ssh.lib 的参数逐条拆配置阶段的核心目标是只产出一个静态库ssh.lib不产 dll、不产导入库、不编示例程序和测试OpenSSL 和 zlib 的路径明确指到静态库版本CRT 与前面的依赖链对齐。libssh 的 CMake 选项不算多但默认值对“只要静态库”这个诉求并不友好——静态库和共享库默认都开两个目标的输出名都叫ssh共享库还会额外生成一个同名的ssh.lib导入库放在同一目录下必然互相覆盖。3.1 生成器、架构与构建类型先建目录再配置源码目录和构建目录分开是最基本的纪律mkdir D:\build\libssh-0.10.3-x64 cd D:\build\libssh-0.10.3-x64 cmake -G Visual Studio 14 2015 Win64 ^ -DCMAKE_BUILD_TYPERelease ^ -DCMAKE_INSTALL_PREFIXD:/deps/libssh-0.10.3/install-x64 ^ ../libssh-0.10.3-G决定架构64 位工程必须用Win64后缀否则后面链接 64 位 OpenSSL 时报LNK1112: module machine type x64 conflicts with target machine type x86。CMAKE_BUILD_TYPE对多配置生成器VS 工程其实不生效真正决定 Debug/Release 的是构建时传的--config但写上不影响也方便以后换 Ninja 生成器时复用同一条命令。CMAKE_INSTALL_PREFIX建议指到一个独立目录install目标会把头文件和库按标准结构摆好后面接项目只认这一个路径。第一次配置完不要急着编译先翻一遍输出里的依赖探测结果。CMake 会打印 OpenSSL 和 zlib 的查找情况Found OpenSSL: ...后面的路径是不是你指定的那个 x64 目录Found ZLIB: ...指向的是不是zlibstatic.lib。这一步看三十秒能省掉后面半小时的链接报错。3.2 库类型开关只要静态 ssh.lib 时关掉哪些目标完整的配置命令带上了我这套环境里固定会加的开关cmake -G Visual Studio 14 2015 Win64 ^ -DCMAKE_INSTALL_PREFIXD:/deps/libssh-0.10.3/install-x64 ^ -DWITH_STATIC_LIBON ^ -DWITH_SHARED_LIBOFF ^ -DWITH_ZLIBON ^ -DZLIB_ROOTD:/deps/zlib-1.2.11/x64 ^ -DOPENSSL_ROOT_DIRD:/deps/openssl-1.1.1w/x64 ^ -DOPENSSL_USE_STATIC_LIBSON ^ -DWITH_SERVEROFF ^ -DWITH_EXAMPLESOFF ^ -DWITH_TESTINGOFF ^ -DCMAKE_C_FLAGS/MT ^ -DCMAKE_C_FLAGS_RELEASE/MT /O2 /DNDEBUG ^ ../libssh-0.10.3WITH_STATIC_LIBON和WITH_SHARED_LIBOFF必须成对出现只开静态不关共享两个目标会抢同一个输出名WITH_ZLIBON表示启用压缩通道前提是ZLIB_ROOT指对了WITH_SERVEROFF关掉服务端代码客户端场景用不上编进去只会多出一堆依赖WITH_EXAMPLESOFF和WITH_TESTINGOFF同理示例程序在 Windows 下经常因为缺pthread之类的东西编不过白白拖慢进度。OPENSSL_USE_STATIC_LIBSON是让 CMake 的 FindOpenSSL 优先挑.lib而不是.dll对应的导入库但这个变量在不同 CMake 版本里行为不完全一致所以OPENSSL_ROOT_DIR也要一起给双保险。如果你的 OpenSSL 是自编译的、目录结构不规范FindOpenSSL 照样可能找错后面排查章节会讲怎么用三个显式变量把它按死。CMAKE_C_FLAGS和CMAKE_C_FLAGS_RELEASE两处都写/MT是因为 VS 工程默认给 Release 配置塞/MD只改全局CMAKE_C_FLAGS会被配置级的默认值覆盖。如果你的主工程是/MD这两行整体去掉即可但 OpenSSL 和 zlib 也必须跟着是/MD不能一半一半。3.3 OpenSSL/zlib 路径与 /MT 运行时的对齐配置完成后别只看 CMake 的Configuring done要打开生成的CMakeCache.txt核对几个关键变量findstr /i OPENSSL ZLIB CMAKE_C_FLAGS CMakeCache.txt重点看三行OPENSSL_CRYPTO_LIBRARY指向的是不是libcrypto.lib的完整路径、ZLIB_LIBRARY是不是zlibstatic.lib、CMAKE_C_FLAGS:STRING里有没有/MT。如果OPENSSL_CRYPTO_LIBRARY为空但OPENSSL_ROOT_DIR有值说明 FindOpenSSL 没认这个目录结构需要手动补-DOPENSSL_INCLUDE_DIRD:/deps/openssl-1.1.1w/x64/include ^ -DOPENSSL_CRYPTO_LIBRARYD:/deps/openssl-1.1.1w/x64/lib/libcrypto.lib ^ -DOPENSSL_SSL_LIBRARYD:/deps/openssl-1.1.1w/x64/lib/libssl.lib这三个变量是 FindOpenSSL 的最终落点优先级高于OPENSSL_ROOT_DIR指哪打哪不会再猜。我在几台不同机器上装这套环境只要 OpenSSL 是自己编的、没有走安装包的标准目录最后都得靠这三行稳住。还有一点容易被忽略/MT是编译选项但 libssh 的 CMake 里可能存在个别目标单独设置了MSVC_RUNTIME_LIBRARY或者硬编码的编译选项配置完最好在生成的.vcxproj里搜一下RuntimeLibrary确认没有MultiThreadedDLL混进来。这一步属于血泪经验我见过一次静态库目标被单独设成/MD结果整个库编出来是好的一链接就 LNK2038查了半天才发现是工程文件里的属性覆盖。4. 构建与产物整理从 .sln 到可链接的 ssh.lib配置过了只是纸面上通了真正的产物要等编译跑完才算数。libssh 0.10.3 的源码量不大x64 Release 配置在普通开发机上几分钟能编完但如果前面某个依赖没对齐错误会集中爆发在链接阶段看起来像是“编了一大堆东西最后失败了”。分目标构建能把问题定位到具体环节比一把梭--build .强。4.1 命令行构建与 Release/Debug 分开出目录先只编静态库目标确认它能独立产出cmake --build . --config Release --target ssh -- /m:4--target ssh是 libssh 静态库的目标名CMake 生成的工程里静态库目标就叫ssh这一点在配置阶段就能从CMakeCache.txt附近的生成文件里确认。-- /m:4是透传给 MSBuild 的并行编译参数4 是并行项目数机器核多可以加到 8。只编这一个目标的好处是如果 OpenSSL 头文件路径不对错误会立刻停在这个目标上而不是淹没在示例程序的报错里。编过之后再走安装目标把头文件和库归拢到统一目录cmake --build . --config Release --target install安装完成后D:/deps/libssh-0.10.3/install-x64下应该是这样的结构include/libssh/放libssh.h、callbacks.h、sftp.h、server.h等头文件lib/放ssh.lib。如果lib目录下同时出现ssh.dll和ssh.lib说明共享库目标没关干净那个ssh.lib是导入库不是静态库后面链接会出现__imp_前缀的未解析符号——这是最容易误判的一种情况文件名叫对了内容不对。Debug 和 Release 我建议用两个独立的构建目录mkdir D:\build\libssh-0.10.3-x64-debug cd D:\build\libssh-0.10.3-x64-debug cmake -G Visual Studio 14 2015 Win64 ^ -DCMAKE_INSTALL_PREFIXD:/deps/libssh-0.10.3/install-x64-debug ^ -DWITH_STATIC_LIBON -DWITH_SHARED_LIBOFF ^ -DWITH_SERVEROFF -DWITH_EXAMPLESOFF -DWITH_TESTINGOFF ^ -DOPENSSL_ROOT_DIRD:/deps/openssl-1.1.1w/x64-debug ^ -DZLIB_ROOTD:/deps/zlib-1.2.11/x64-debug ^ -DCMAKE_C_FLAGS/MTd ^ -DCMAKE_C_FLAGS_DEBUG/MTd /Od /Zi ^ ../libssh-0.10.3Debug 版本要用/MTd和 Debug 版依赖混用 Release 的libcrypto.lib一样会 LNK2038。很多团队最后只在 Release 下用 SSHDebug 版干脆不编这也是一种取舍但要在项目文档里写清楚别让下一个人以为 Debug 目录坏了。4.2 检查产物头文件、lib、依赖清单三件套产物到手先做三项检查比直接扔进项目里试错快得多。第一项看符号有没有进去dumpbin /symbols ssh.lib | findstr /c:ssh_new | findstr /c:External能搜到_ssh_new这样的外部符号说明静态库里有这个函数。如果搜不到要么是目标没编要么是你拿到的是导入库——导入库里只有__imp_ssh_new这类跳转桩。第二项看 CRT 标记dumpbin /directives ssh.lib | findstr /i defaultlib输出应当包含LIBCMT对应/MT不该出现MSVCRT。第三项是把最终要链接的库列成一张清单放进项目文档里后面谁接这个库都照单抓药库文件作用来源ssh.liblibssh 静态库本体本机编译产物libcrypto.lib加密算法OpenSSL 静态库libssl.libTLS 握手OpenSSL 静态库zlibstatic.lib压缩通道zlib 静态库ws2_32.libWinsockWindows SDKcrypt32.lib证书相关Windows SDKuserenv.libOpenSSL 静态库依赖Windows SDKadvapi32.lib注册表/加密服务Windows SDK这张表里后四个是系统库容易被漏掉尤其是userenv.lib它不在常规的“网络程序必备库”清单里但 OpenSSL 静态库会引用它漏了就是几条 LNK2019。链接顺序上ssh.lib放最前OpenSSL 两个库随后zlib 和系统库收尾MSVC 对顺序的容忍度比 GCC 高但按依赖顺序排总不会错。5. 排查记录五个让 libssh 静态库白编的坑这套环境我装过不止一次出问题的位置高度集中配置阶段找错库、链接阶段 CRT 或宏不对、运行阶段没初始化。下面五条都按“现象—原因—解决”记遇到同类表现可以直接对号。5.1 配置阶段的两处误判现象一CMake 报Could not find OpenSSL但OPENSSL_ROOT_DIR明明指了目录。原因多半是目录结构不符合 FindOpenSSL 的预期——它要的是include/openssl/ssl.h和lib/libcrypto.lib这种标准布局自编译时如果--prefix指到了x64但库放在x64/lib64或者头文件在x64/include/openssl之外它就找不到。另一个常见原因是 CMake 缓存里存着上一次的错误路径重新配置时没清。 解决删掉构建目录重来或者直接上-DOPENSSL_INCLUDE_DIR、-DOPENSSL_CRYPTO_LIBRARY、-DOPENSSL_SSL_LIBRARY三个显式变量绕开查找逻辑。清缓存这一步别省CMakeCache.txt里的旧值优先级很高。现象二zlib 被找到了但链接时报cannot open file zlib.lib。原因是 zlib 源码编译会同时产出zlib.lib动态库导入库和zlibstatic.lib静态库FindZLIB 在某些版本里优先挑了zlib.lib名字对上了但文件根本不在安装目录里或者存在但内容不对。 解决用-DZLIB_LIBRARY显式指向zlibstatic.lib配置完在CMakeCache.txt里核对ZLIB_LIBRARY的实际值。我一般还会把zlibstatic.lib复制一份改名成zlib.lib放在独立目录里专门给这种找不准的情况兜底但这是权宜之计正式环境还是显式指定。5.2 链接阶段的两处翻车现象三LNK2038: mismatch detected for RuntimeLibrary: value MD_DynamicRelease doesnt match value MT_StaticRelease。原因是参与链接的库 CRT 不一致最常见的是 libssh 用了/MT、OpenSSL 静态库是默认的/MD或者反过来。这类错误信息里会直接点名两个模块看哪个是MD_DynamicRelease就知道谁没对齐。 解决统一重编依赖。主工程是/MT就把 OpenSSL 用CFLAG/MT重编、zlib 用-DCMAKE_C_FLAGS/MT重编、libssh 两处CMAKE_C_FLAGS都写/MT。别试图用/NODEFAULTLIB或忽略特定库来绕过绕过了链接运行期堆分配跨 CRT 照样炸。现象四LNK2019: unresolved external symbol __imp_ssh_new referenced in function ...。原因是使用方没有定义LIBSSH_STATIC宏。libssh 的头文件在 Windows 下会根据这个宏决定函数声明是__declspec(dllimport)还是普通声明没定义就按 dllimport 展开编译器于是生成对__imp_ssh_new的引用而静态库里只有_ssh_new。 解决在使用方的预处理器定义里加LIBSSH_STATIC对应编译选项/DLIBSSH_STATIC。这个宏不会自动带进来必须手写我见过有人把它写进.c文件开头结果只在那一份文件里生效其他文件照样报错正确位置是工程级或目标级的预处理器定义。5.3 运行阶段的一处黑匣子现象五程序编译链接都过一启动就崩或者ssh_new()返回 NULL日志里没有任何有用信息。原因是 Windows 下没调ssh_init()。libssh 在 Windows 上依赖 Winsock使用前必须初始化ssh_init()内部做的就是这件事不调它后面的 socket 创建会直接失败而失败点往往在很深的调用栈里表现成一个没有上下文的 NULL。 解决进程启动时调一次ssh_init()退出前调ssh_finalize()成对出现。多线程场景下初始化一次就够别每个线程都调。另一个同类的坑是把ssh.lib编成了静态库、但 exe 同目录下又恰好有一个旧版ssh.dll加载器优先挂了 dll行为对不上源码查起来像玄学——确认发布目录里没有残留 dll也是排查清单上的一项。6. 验证 ssh.lib 能跑二十行 C 程序加一条链接命令编完不验证等于把风险留给下一个接项目的人。我验证静态库的习惯是写一个最小程序只调公开 API不碰业务逻辑能初始化、能创建会话、能读出版本号就说明库本体、依赖链、CRT、宏定义四件事全部对齐。6.1 最小验证程序/* verify_ssh.c —— 只验证 libssh 静态库能否正常链接与初始化 */ #include stdio.h #include libssh/libssh.h /* 使用静态库时必须先定义 LIBSSH_STATIC */ int main(void) { if (ssh_init() ! 0) { /* Windows 下初始化 Winsock */ printf(ssh_init failed\n); return 1; } printf(libssh version: %s\n, ssh_version(0)); /* 传 0 表示不校验最低版本 */ ssh_session s ssh_new(); /* 创建会话对象验证内存分配路径 */ if (s NULL) { printf(ssh_new failed\n); ssh_finalize(); return 2; } /* 设一个不会真连的地址只为验证选项设置接口可用 */ ssh_options_set(s, SSH_OPTIONS_HOST, 127.0.0.1); ssh_options_set(s, SSH_OPTIONS_PORT_STR, 2222); ssh_free(s); ssh_finalize(); /* 与 ssh_init 成对释放 */ printf(smoke test ok\n); return 0; }ssh_version(0)返回的是库自身版本字符串参数传 0 表示不要求最低版本只取值不校验ssh_new()内部会分配会话结构失败通常意味着底层依赖有问题SSH_OPTIONS_HOST和SSH_OPTIONS_PORT_STR是选项枚举和字符串端口设成 127.0.0.1:2222 是为了不触发真实网络连接只走参数赋值路径。最后ssh_finalize()必须调否则进程退出时 Winsock 清理不干净在多线程宿主里可能引发退出卡死。6.2 链接命令与结果判定cl /nologo /MT /W3 /DLIBSSH_STATIC verify_ssh.c ^ /ID:\deps\libssh-0.10.3\install-x64\include ^ /link /LIBPATH:D:\deps\libssh-0.10.3\install-x64\lib ^ /LIBPATH:D:\deps\openssl-1.1.1w\x64\lib ^ /LIBPATH:D:\deps\zlib-1.2.11\x64\lib ^ ssh.lib libcrypto.lib libssl.lib zlibstatic.lib ^ ws2_32.lib crypt32.lib userenv.lib advapi32.lib/MT与库的编译选项一致/DLIBSSH_STATIC是静态链接必须的宏三个/LIBPATH分别指向 libssh、OpenSSL、zlib 的库目录。命令里没有ssh.dll相关的任何东西如果这条命令能过说明产物是货真价实的静态库。现象指向的问题编译报Cannot open include file: libssh/libssh.h安装目录或/I路径不对LNK2019 __imp_ssh_new漏了/DLIBSSH_STATICLNK2038 RuntimeLibrary mismatchCRT 不一致回查第 5.2 节能链接但运行输出ssh_init failed依赖库缺失或版本错配输出libssh version: 0.10.3且smoke test ok全链路通过跑出最后一行的时候把verify_ssh.c和这份链接命令一起放进项目的tools/目录下次换机器、换 VC 小版本、换 OpenSSL 补丁版先跑它再动业务代码。从那以后我每次编完ssh.lib都强制走一遍这个小程序和dumpbin /directives的双重检查宁可多花三分钟也不想再在业务代码里对着一条__imp_开头的未解析符号猜半天。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →