
简介面向使用Visual Studio 2015在Windows win32/x64平台进行C gRPC开发的工程师压缩包提供了一套开箱即用的编译依赖环境。内含446个文件以390个头文件和36个库文件为主体辅以12个proto描述文件、4个编译器插件protoc及gRPC C插件以及inc、modulemap等配置总体积约44.55MB。头文件与库文件分别对应DebugMDd和ReleaseMD两种运行时可直接链接到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. 先看 ABIMDd/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 默认把它设为 2STL 迭代器会带额外的调试状态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_LEVELDebug/MDdvcruntime140d.dll msvcp140d.dll定义2Release/MDvcruntime140.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 version1.0 encodingutf-8? Project ToolsVersion14.0 xmlnshttp://schemas.microsoft.com/developer/msbuild/2003 !-- 所有 Debug 配置运行库锁死 /MDd -- ItemDefinitionGroup Condition$(Configuration)Debug ClCompile RuntimeLibraryMultiThreadedDebugDLL/RuntimeLibrary /ClCompile /ItemDefinitionGroup !-- 所有 Release 配置运行库锁死 /MD -- ItemDefinitionGroup Condition$(Configuration)Release ClCompile RuntimeLibraryMultiThreadedDLL/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 AdditionalDependenciesgrpc.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 AdditionalDependenciesgrpc.lib;grpc.lib;libprotobuf.lib;%(AdditionalDependencies)/AdditionalDependencies /Link /ItemDefinitionGroup /Project这段 props 的逻辑分三块。第一块是 RuntimeLibraryMultiThreadedDebugDLL 对应 /MDdMultiThreadedDLL 对应 /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、VS2019VS 可能会自动给你选成 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 是 /MDdRelease 是 /MD。但工程里一旦混入了别的第三方库或者有人手动改过 CMAKE_MSVC_RUNTIME_LIBRARY配置就可能悄悄漂移。我会在顶层 CMakeLists 里显式锁一次if(NOT DEFINED CMAKE_MSVC_RUNTIME_LIBRARY) set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$$CONFIG:Debug:DebugDLL) 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:DebugDLL)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两者不相等。原因包是按 VS2015v140编的你的工程却用了 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: DebugRelease 配置应输出 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 不匹配。从那以后我坚持接到预编译包先验边界再谈业务。希望帮到你。本文还有配套的精品资源点击获取