简介:面向使用Visual Studio 2015在Windows win32/x64平台进行C++ gRPC开发的工程师,压缩包提供了一套开箱即用的编译依赖环境。内含446个文件,以390个头文件和36个库文件为主体,辅以12个proto描述文件、4个编译器插件(protoc及gRPC C++插件)以及inc、modulemap等配置,总体积约44.55MB。头文件与库文件分别对应Debug(MDd)和Release(MD)两种运行时,可直接链接到VS2015项目,免去从源码构建gRPC的繁琐步骤。库文件包含动态库与静态库,protoc与grpc_cpp_plugin可执行程序能生成服务端/客户端代码,开发者在win32或x64架构下均能快速搭建基于HTTP/2和Protobuf的通信服务。该资源已有1306人浏览学习,适合需要基于VS2015维护旧项目或学习旧版gRPC集成的C++开发者。
1. 先别急着解压:这份 VS2015 的 rar 解决的是 ABI 分发问题
“grpc头文件和库VS2015-win32_x64-Debug(MDd)_Release(MD).rar”这类预编译包,干的事一句话就能讲完:把 Windows 上最难伺候的 gRPC 依赖链,压缩成一个能直接解压扔进工程的“环境快照”。它按 VS2015、win32/x64、Debug(MDd)/Release(MD) 分好层,你要做的不是从零编译 gRPC 和 protobuf,而是按自己工程的架构与运行库,选对目录、配好链接器、装对 DLL。适合用 Visual Studio 2015 写 C++ 客户端或服务端、又不想折腾完整 CMake 交叉编译脚本的开发者。包名里最值得先看懂的也不是 gRPC,而是 MDd/MD 这两个后缀——它决定了这份库能不能和你的工程和平共处。
2. 先看 ABI:MDd/MD 决定这份库能不能用,而不是 grpc 版本
2.1 为什么 Windows 上的第三方库必须按 Debug/Release 分开发
Windows 上用 MSVC 编译 C++,运行库的选择不是小事,它是 ABI(二进制接口)的一部分。VS2015 里,/MDd 和 /MD 分别代表“多线程调试 DLL”和“多线程 DLL”:前者链接 vcruntime140d.dll、msvcp140d.dll,并定义 _DEBUG 宏;后者链接正式版运行库,不定义 _DEBUG。gRPC 这种大量使用 STL 容器的库,一旦和调用方在运行库选项上不一致,轻则链接警告,重则运行期直接崩。
更隐蔽的是 _ITERATOR_DEBUG_LEVEL。Debug 下 MSVC 默认把它设为 2,STL 迭代器会带额外的调试状态,vector、string 这些类的内存布局也随之变化;Release 下默认为 0。一个用 MDd 编译的模块,把一个 std::string 传给用 MD 编译的模块,两个模块对这个类的布局理解都不一样,跨模块传递对象就是典型的“黑匣子崩溃”——报错不在现场,而在几个月后的生产环境。
除了运行库,还有编译器大版本。VS2015 对应 _MSC_VER 1900 系列,VS2017 是 1910 以上,VS2019 是 1920 以上。只要平台工具集不是 v140,哪怕运行库都选 MDd,链接器依然会报 LNK2038,因为 _MSC_VER 对不上。所以包名写“VS2015”,就意味着你的工程平台工具集必须锁在 Visual Studio 2015 (v140),不能因为装了新 IDE 就顺手切成 v142。
| 配置 | 编译器开关 | CRT 依赖 | _DEBUG | _ITERATOR_DEBUG_LEVEL |
|---|---|---|---|---|
| Debug | /MDd | vcruntime140d.dll + msvcp140d.dll | 定义 | 2 |
| Release | /MD | vcruntime140.dll + msvcp140.dll | 未定义 | 0 |
一个小误区:有人觉得“我只要在预处理宏里手动加 _DEBUG 就能模拟 Debug”。不行。运行库 DLL 的名字、迭代器级别、operator new/delete 的实现入口都不一样,这些不是宏能补齐的。调试配置就用 /MDd,发布配置就用 /MD,不要自己造组合。
2.2 解压后怎么认目录和文件,别拿错库
拿到包先别急着全选解压。先看目录结构,常见会长这样:
include/ # gRPC 与 protobuf 的头文件 lib/ x64/ Debug(MDd)/ # x64 调试版,链接 /MDd Release(MD)/ # x64 发布版,链接 /MD win32/ Debug(MDd)/ # 32 位调试版 Release(MD)/ # 32 位发布版目录标签不是随便起的。Debug(MDd) 和 Release(MD) 两套目录里的库文件很可能文件名一模一样,但二进制不通用。拿错目录,编译可能都能通过,因为头文件是一样的,链接期才会炸出 LNK2038,或者更惨——链接过了,运行期因为迭代器布局不一致而崩溃。
文件类型也要分清。.lib 有两种:一种是导入库,体积小,里面只有符号表,运行时要配合同名 .dll;另一种是静态库,体积大,代码直接进你的 exe。用导入库时,编译链接只需要 .lib,但运行 exe 必须能找到对应 .dll,否则启动直接报“找不到 grpc++.dll”。用静态库则没有这个问题,但 exe 会变大,且需要确保包内所有依赖(比如 protobuf、zlib)也都是静态链接版本,否则还是有一堆 dll 要带。判断方法是看 .lib 文件大小:几十 KB 到几百 KB 基本是导入库,几 MB 以上多半是静态库。
还有一类文件是 .pdb。它对运行没有影响,只在调试时用来匹配符号。真正发布到别的机器时,pdb 不需要带,grpc 的 dll 和你的 exe 带过去就行。但调试阶段建议留着,否则进了 gRPC 内部函数,调用栈全是一堆地址,没法看。
2.3 一个硬性原则:lib 与 dll 必须来自同一层目录
我见过最典型的误用是:Debug(MDd) 目录下的 .lib 配 Release(MD) 目录下的 .dll。理由是“反正符号都一样”。符号确实一样,但实现可能不一样,尤其是 STL 容器的内存布局差异。Debug 库的内部对象可能带着额外的调试字段,Release 的 dll 拿到的对象却按正式布局去解析,不崩才怪。
为了避免这种问题,我给团队定的规矩是:整个解决方案里,gRPC 相关路径只出现一次。用第 3 章的 props 文件把路径集中管理,Debug 配置只指到 Debug(MDd),Release 配置只指到 Release(MD),任何人都不许手改链接器目录。这样最省心。
3. 在 VS2015 里手动接上这份库:属性表一条路走通
3.1 写一份 props 文件把路径和运行库锁死
在 VS2015 里手动配置第三方库,最常规的操作是打开“项目属性”,逐项填包含目录、库目录、附加依赖项。但这么做有三个问题:Debug/Release 要填两遍、x64/Win32 要填四遍、换台机器全得重来。我的做法是写一份 props 文件,让整个团队共用同一份二进制配置。
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- 所有 Debug 配置:运行库锁死 /MDd --> <ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'"> <ClCompile> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> </ClCompile> </ItemDefinitionGroup> <!-- 所有 Release 配置:运行库锁死 /MD --> <ItemDefinitionGroup Condition="'$(Configuration)'=='Release'"> <ClCompile> <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> </ClCompile> </ItemDefinitionGroup> <!-- Debug | x64 指向包内 x64\Debug(MDd) 目录 --> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ClCompile> <AdditionalIncludeDirectories>$(ProjectDir)third_party\grpc\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <AdditionalLibraryDirectories>$(ProjectDir)third_party\grpc\lib\x64\Debug(MDd);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> <AdditionalDependencies>grpc++.lib;grpc.lib;libprotobuf.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <!-- Release | x64 指向包内 x64\Release(MD) 目录 --> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ClCompile> <AdditionalIncludeDirectories>$(ProjectDir)third_party\grpc\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> <AdditionalLibraryDirectories>$(ProjectDir)third_party\grpc\lib\x64\Release(MD);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> <AdditionalDependencies>grpc++.lib;grpc.lib;libprotobuf.lib;%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> </Project>这段 props 的逻辑分三块。第一块是 RuntimeLibrary:MultiThreadedDebugDLL 对应 /MDd,MultiThreadedDLL 对应 /MD,它从编译层面保证整个工程的使用方式与包名一致。第二块是 AdditionalIncludeDirectories:让编译器能找到 grpcpp/grpcpp.h 和 google/protobuf 的头文件。第三块是 AdditionalLibraryDirectories 和 AdditionalDependencies:告诉链接器去哪个目录找 .lib、具体链接哪几个库。
使用步骤很简单:把 rar 解压到工程目录下的 third_party\grpc,然后在 VS2015 的“属性管理器”里右键项目,选择“添加现有属性表”,选中这份 .props,它会对当前配置立即生效。Debug|x64 用 Debug(MDd) 目录,Release|x64 用 Release(MD) 目录,条件判断交给 MSBuild,不需要你手动切来切去。
参数提醒:grpc++.lib、grpc.lib、libprotobuf.lib 是按常见命名写的,不同版本的包可能略有出入,以你实际解压出来的 .lib 文件名为准。另外,如果你不打算用 32 位,就把 win32 两段删掉,免得属性表里出现指向不存在目录的配置。
3.2 配置管理器、平台工具集、输出目录三处别漏
props 文件只解决“路径和运行库”,还有两个开关不在它管的范围里。第一是平台工具集。项目属性 → 常规 → 平台工具集,必须选“Visual Studio 2015 (v140)”。如果机器上还装了 VS2017、VS2019,VS 可能会自动给你选成 v141 或 v142,这时候链接会报 _MSC_VER 不匹配。第二是配置管理器里的活动解决方案平台。默认可能是 Win32,你得在“配置管理器”里新建 x64 平台,并在工具栏上把解决方案平台切到 x64,否则你改了属性页也可能根本没改到当前生效的那一页。
第三件容易被忽略的事是 DLL 的部署。用导入库时,编译链接只是第一步,exe 跑起来还需要 grpc 的 dll。我一般会加一个生成后事件,把包内对应目录的 dll 全部拷到输出目录:
xcopy /Y /D "$(ProjectDir)third_party\grpc\lib\x64\Debug(MDd)\*.dll" "$(OutDir)"xcopy 的 /Y 表示不询问直接覆盖,/D 表示只拷贝比目标新的文件,避免每次编译都全量复制。路径里的 Debug(MDd) 要随配置切换,所以更省事的做法是分 Debug 和 Release 两个配置分别写两条命令,或者干脆把 dll 目录直接加进 PATH 环境变量——后者只适合本机调试,不适合做安装包。
3.3 同时接 protobuf:版本号要保持同步
gRPC 的 lib 内部已经引用了 protobuf 的符号,但你的工程里如果还要直接调用 protobuf 接口,比如手动构造 Message 或操作反射,就必须单独链接一份 protobuf 库。这里的坑在于版本同步:gRPC 和 protobuf 的版本是强耦合的,老版本的 gRPC 经常依赖固定小版本的 protobuf,差一个 minor 版本都可能出现链接告警或运行期行为异常。
验证方法很土但有效:写两行代码,打印 gRPC 和 protobuf 的版本宏。
#include <grpcpp/grpcpp.h> #include <google/protobuf/stubs/common.h> #include <cstdio> int main() { #ifdef GRPC_VERSION std::printf("gRPC: %s\n", GRPC_VERSION); #endif std::printf("Protobuf: %d\n", GOOGLE_PROTOBUF_VERSION); return 0; }GRPC_VERSION 是字符串宏,GOOGLE_PROTOBUF_VERSION 是整数宏。把输出结果和你预编译包发布时对应的版本说明对一下,不一致就说明你工程里 include 的 protobuf 头文件版本和库版本不是同一套,需要统一。如果 rar 里自带 protobuf 头文件和 lib,那就直接用它,不要再从系统目录里找一份“新版本”。
4. 换到 CMake 工程:用一个 Imported Target 管住路径
4.1 先找 CMake 配置,没有就手写 Imported Target
很多预编译包会附带 cmake 子目录,里面有 gRPCConfig.cmake 或 grpc-config.cmake。如果有,优先用 find_package(gRPC CONFIG),这是最省事的方式。但有些精简包只给头文件和 lib,没有 CMake 配置,这时我不会让团队成员去裸链路径,而是手写一个 IMPORTED target,把路径和配置状态全部封装进去。
# 假设包解压到 ${CMAKE_SOURCE_DIR}/third_party/grpc if(NOT DEFINED GRPC_ROOT) set(GRPC_ROOT "${CMAKE_SOURCE_DIR}/third_party/grpc") endif() add_library(grpc++ UNKNOWN IMPORTED) set_target_properties(grpc++ PROPERTIES IMPORTED_LOCATION_DEBUG "${GRPC_ROOT}/lib/x64/Debug(MDd)/grpc++.lib" IMPORTED_LOCATION_RELEASE "${GRPC_ROOT}/lib/x64/Release(MD)/grpc++.lib" INTERFACE_INCLUDE_DIRECTORIES "${GRPC_ROOT}/include" ) target_link_libraries(grpc_demo PRIVATE grpc++)这段代码的关键在 IMPORTED_LOCATION_DEBUG 和 IMPORTED_LOCATION_RELEASE。CMake 在 Debug 配置下会自动选取带 _DEBUG 后缀的路径,在 Release 配置下选取 _RELEASE 后缀的路径,这就避免了在 CMakeLists 里写 if(MSVC) 分支判断。用 UNKNOWN IMPORTED 是因为我们不确定这个 .lib 是静态库还是导入库,让 CMake 不做类型假设,直接把路径传给链接器,反而最稳。
同时要注意,这个 target 只解决了 grpc++ 本身。如果工程里还直接用 protobuf,就照同样方式再写一个 protobuf 的 IMPORTED target,并在 target_link_libraries 里一并链接。不要图省事把 protobuf 的 .lib 直接塞进附加依赖项,那样 IDE 的 CMake 缓存一刷新,路径问题会变得很难查。
4.2 让 CMake 与 MSVC 运行库自动对齐
CMake 在 VS2015 上生成工程时,默认运行库取决于生成器,一般来说 Debug 是 /MDd,Release 是 /MD。但工程里一旦混入了别的第三方库,或者有人手动改过 CMAKE_MSVC_RUNTIME_LIBRARY,配置就可能悄悄漂移。我会在顶层 CMakeLists 里显式锁一次:
if(NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL") endif()这个写法的原理是生成器表达式:Debug 配置下展开为 MultiThreadedDebugDLL,也就是 /MDd;其它配置下展开为 MultiThreadedDLL,也就是 /MD。外面套一层 if(NOT DEFINED) 是为了允许个别工程通过命令行覆盖这个值,比如某些静态库工程确实需要 /MT。
注意:CMAKE_MSVC_RUNTIME_LIBRARY 是 CMake 3.15 才引入的。如果你还在用 3.14 或更早版本,这个变量会被静默忽略,运行库又会退回默认值。我的习惯是在 README 里写死“CMake 最低版本 3.16”,省得有人用老版本踩出奇奇怪怪的链接告警。另一个更保守的做法是在具体 target 上设置属性:
set_property(TARGET grpc_demo PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")target 级别的好处是只影响当前目标,不会误伤解决方案里的其它模块;坏处是每个用到 gRPC 的 target 都要写一遍,容易漏。
4.3 protoc 与 grpc_cpp_plugin 的版本对不齐,是最难查的坑
链接问题和运行库问题都有明确的报错,编译期版本不一致才是真正折磨人的。你把 .proto 文件用本机装的 protoc 生成 .pb.cc,编译能过,但链接 grpc 库时可能报一堆 unresolved external symbol,或者干脆在运行期出现“bad allocation”。原因通常是:你的 protoc 版本比预编译包里的 protobuf 库新太多,生成的代码调用了旧库不存在的符号。
这类问题不会每次报错,报错也未必能一眼看出关联。排查顺序是:先用 3.3 的宏探针确认 protobuf 版本,再确认 protoc 的版本,两者必须一致。如果包内没有 protoc.exe,就找和包内 protobuf 库同版本的 protoc 生成代码,不要图方便用最新版。
5. 高频翻车点:与这套 VS2015 库相关的 4 个报错
5.1 LNK2038:_MSC_VER 不匹配
现象:链接器报 LNK2038 mismatch detected for '_MSC_VER',错误里同时给出两个值,一个是你工程的 _MSC_VER,一个是库的 _MSC_VER,两者不相等。
原因:包是按 VS2015(v140)编的,你的工程却用了 v141 或 v142 工具集。VS2015 之后的版本二进制兼容性已经打破,MSVC 版本号直接写进所有 obj 文件的头,链接器一比对就不通过。
解决:项目属性 → 常规 → 平台工具集,改成“Visual Studio 2015 (v140)”。如果属性页里看不到这个选项,说明机器上没装 VS2015 相关组件,装完再切。还有一种情况是工程文件本身是在新版本 IDE 里创建的,切完 v140 后可能提示“需要升级工具集”,此时直接选“否”,保持 v140 不变。
5.2 LNK2038:_ITERATOR_DEBUG_LEVEL 不匹配
现象:LNK2038 mismatch detected for '_ITERATOR_DEBUG_LEVEL',一个值是 0,另一个值是 2。
原因:_ITERATOR_DEBUG_LEVEL 为 0 的是 Release 运行库(/MD),为 2 的是 Debug 运行库(/MDd)。这个报错本质上是 Debug/Release 库混用了。常见场景:Debug 工程链接时把包内 Release(MD) 目录的 .lib 加进了附加依赖项,或者反过来 Release 工程用了 Debug(MDd) 的库。
解决:打开项目属性,C/C++ → 代码生成 → 运行库,Debug 配置选“多线程调试 DLL (/MDd)”,Release 配置选“多线程 DLL (/MD)”。然后把链接器 → 输入 → 附加依赖项里的路径全部核对一遍,确保 Debug 只指向 x64\Debug(MDd),Release 只指向 x64\Release(MD)。这里没有捷径,路径写错一个字符,报错都是这一条。
5.3 LNK1112 或 LNK2001:架构选错
现象:链接报 LNK1112: module machine type 'x64' conflicts with target machine type 'x86';或者报一堆 unresolved external symbol __imp_xxx,但库目录看起来没问题。
原因:LNK1112 是比较明显的架构冲突,通常是把 x64 的 .lib 配进了 Win32 工程。LNK2001 则更隐蔽,很可能是“活动解决方案平台”还是 Win32,你改了属性页面但改的不是当前生效的 x64 页。
解决:先看 VS2015 工具栏上的“解决方案平台”下拉框,切到 x64。再打开“配置管理器”,确认“活动解决方案平台”也是 x64,且所有项目都有 x64 配置。最后检查链接器 → 高级 → 目标计算机,x64 工程应为 MachineX64。这几个地方全部对齐后,LNK2001 往往就消失了。
5.4 运行时报“找不到 DLL”或“无法定位程序输入点”
现象:编译和链接全部通过,双击 exe 弹出“找不到 grpc++.dll”,或者“无法定位程序输入点 xxx 于动态链接库 grpc++.dll”。
原因:前一种是 DLL 不在 exe 目录或 PATH 里。导入库只在链接期起作用,运行期该 DLL 必须能被系统找到。后一种多半是 DLL 版本混了,比如把 Debug(MDd) 的 dll 和 Release(MD) 的 lib 配在一起,或者 PATH 里有另一个版本的 grpc dll 被优先加载。
解决:把包内对应架构和配置的 .dll 复制到 exe 输出目录,用第 3.2 节的 xcopy 生成后事件可以一劳永逸。如果复制了依然报“无法定位输入点”,用 dumpbin 查一下 exe 实际加载的 DLL 路径:
dumpbin /dependents x64\Debug\grpc_demo.exe这个命令需要在 VS2015 的开发者命令提示符里执行。输出里如果出现多个目录下的 grpc dll,说明 PATH 污染了,把系统 PATH 里其它版本的 gRPC 路径清掉,只保留工程目录的这份即可。
排查这几类问题,我有一个固定的顺序:先看 _MSC_VER,再看 _ITERATOR_DEBUG_LEVEL,再看架构,最后看运行期 DLL 部署。四个边界条件都对齐了,99% 的怪问题都不会发生。
6. 最后教你两手验证:版本探针和依赖检查
库接上之后,先别急着写业务代码,跑一个最小探针确认边界条件,能省下后面排查的大把时间。探针代码不用很复杂,能输出版本宏和当前配置就行。
#include <grpcpp/grpcpp.h> #include <cstdio> int main() { #ifdef GRPC_VERSION std::printf("gRPC: %s\n", GRPC_VERSION); #else std::puts("gRPC macro not found"); #endif #if defined(_DEBUG) std::puts("config: Debug (MDd expected)"); #else std::puts("config: Release (MD expected)"); #endif return 0; }编译运行后,Debug 配置应输出 config: Debug,Release 配置应输出 config: Release。如果 Debug 配置下输出的是 Release 对应内容,说明预处理宏被改动过,或者链接的库不是调试版,这时候直接停下来查属性。
第二个验证手段是 dumpbin。链接和编译都通过了,但你还是不确定 exe 运行时到底依赖哪些 DLL,用 dumpbin 看最直观:
dumpbin /dependents x64\Release\grpc_demo.exe输出会列出 exe 的依赖项,重点看有没有 grpc 相关 dll,以及它们的路径来源。如果依赖列表里出现的是工程目录下的 DLL,说明部署路径正确;如果出现一堆其它安装目录的版本,就要注意 PATH 污染。对于需要交付给别处的 exe,这一步还能顺带确认目标机器需要带哪些运行库文件。
我自己的习惯是,把第 3 章的 props 文件和这一节探针代码放进团队模板,新接手机器的人,第一件事就是跑探针和 dumpbin。最惨的一次教训,是某个同学把 Release(MD) 的 lib 配进了 Debug 工程,编译链接全过,结果程序启动就崩,查了很久才发现是 _ITERATOR_DEBUG_LEVEL 不匹配。从那以后我坚持:接到预编译包先验边界,再谈业务。希望帮到你。
本文还有配套的精品资源,点击获取