简介:面向 Windows 平台 C/C++ 开发者的 libssh-0.10.3 静态库,由 VC2015(vc140)编译,适合需要在自有程序中集成 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 2015(32 位)和Visual Studio 14 2015 Win64(64 位)。CMake 版本我一般钉在 3.2x 这一档,太新的版本对 VS2015 工程格式的保留不如老版本稳,没必要在这上面冒险。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| Visual Studio | 2015(v140) | 安装时勾选“Visual C++”与 Windows SDK |
| CMake | 3.2x | 能生成Visual Studio 14 2015 Win64工程 |
| OpenSSL | 1.1.1 系列静态库 | 与 VC2015 兼容,需自行编译no-shared |
| zlib | 1.2.11 静态库 | 产物名通常是zlibstatic.lib |
| libssh | 0.10.3 | 源码目录保持干净,另建 build 目录 |
检查工具链是否就位,不用打开 IDE,两条命令足够:
cmake --version cl 2>&1 | 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 --prefix=D:/deps/openssl-1.1.1w/x64 nmake nmake install_swVC-WIN64A指定 64 位目标,32 位换成VC-WIN32;no-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_PREFIX="D:/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_TYPE=Release ^ -DCMAKE_INSTALL_PREFIX="D:/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_PREFIX="D:/deps/libssh-0.10.3/install-x64" ^ -DWITH_STATIC_LIB=ON ^ -DWITH_SHARED_LIB=OFF ^ -DWITH_ZLIB=ON ^ -DZLIB_ROOT="D:/deps/zlib-1.2.11/x64" ^ -DOPENSSL_ROOT_DIR="D:/deps/openssl-1.1.1w/x64" ^ -DOPENSSL_USE_STATIC_LIBS=ON ^ -DWITH_SERVER=OFF ^ -DWITH_EXAMPLES=OFF ^ -DWITH_TESTING=OFF ^ -DCMAKE_C_FLAGS="/MT" ^ -DCMAKE_C_FLAGS_RELEASE="/MT /O2 /DNDEBUG" ^ ../libssh-0.10.3WITH_STATIC_LIB=ON和WITH_SHARED_LIB=OFF必须成对出现,只开静态不关共享,两个目标会抢同一个输出名;WITH_ZLIB=ON表示启用压缩通道,前提是ZLIB_ROOT指对了;WITH_SERVER=OFF关掉服务端代码,客户端场景用不上,编进去只会多出一堆依赖;WITH_EXAMPLES=OFF和WITH_TESTING=OFF同理,示例程序在 Windows 下经常因为缺pthread之类的东西编不过,白白拖慢进度。
OPENSSL_USE_STATIC_LIBS=ON是让 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_DIR="D:/deps/openssl-1.1.1w/x64/include" ^ -DOPENSSL_CRYPTO_LIBRARY="D:/deps/openssl-1.1.1w/x64/lib/libcrypto.lib" ^ -DOPENSSL_SSL_LIBRARY="D:/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_PREFIX="D:/deps/libssh-0.10.3/install-x64-debug" ^ -DWITH_STATIC_LIB=ON -DWITH_SHARED_LIB=OFF ^ -DWITH_SERVER=OFF -DWITH_EXAMPLES=OFF -DWITH_TESTING=OFF ^ -DOPENSSL_ROOT_DIR="D:/deps/openssl-1.1.1w/x64-debug" ^ -DZLIB_ROOT="D:/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 下用 SSH,Debug 版干脆不编,这也是一种取舍,但要在项目文档里写清楚,别让下一个人以为 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.lib | libssh 静态库本体 | 本机编译产物 |
libcrypto.lib | 加密算法 | OpenSSL 静态库 |
libssl.lib | TLS 握手 | OpenSSL 静态库 |
zlibstatic.lib | 压缩通道 | zlib 静态库 |
ws2_32.lib | Winsock | Windows SDK |
crypt32.lib | 证书相关 | Windows SDK |
userenv.lib | OpenSSL 静态库依赖 | Windows SDK |
advapi32.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' doesn't 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 ^ /I"D:\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_STATIC |
LNK2038 RuntimeLibrary mismatch | CRT 不一致,回查第 5.2 节 |
能链接但运行输出ssh_init failed | 依赖库缺失或版本错配 |
输出libssh version: 0.10.3且smoke test ok | 全链路通过 |
跑出最后一行的时候,把verify_ssh.c和这份链接命令一起放进项目的tools/目录,下次换机器、换 VC 小版本、换 OpenSSL 补丁版,先跑它再动业务代码。从那以后我每次编完ssh.lib,都强制走一遍这个小程序和dumpbin /directives的双重检查,宁可多花三分钟,也不想再在业务代码里对着一条__imp_开头的未解析符号猜半天。希望帮到你。
本文还有配套的精品资源,点击获取