简介:Windows平台下,一份使用VS2010编译的Thrift 0.11.0完整资源包,适合需要将跨语言RPC框架快速集成到C++工程中的开发者使用。压缩包共158个文件,包含92个头文件、61个C++源文件、2个lib库文件,总大小仅3.12MB。Thrift通过IDL定义服务接口,编译器自动生成多语言代码;这里的lib可直接链接,省去手动配置Boost、OpenSSL和VS2010编译环境的麻烦。源码部分覆盖TNonblockingServer、TServerSocket、TJSONProtocol、THeaderTransport等模块,既支持快速搭建高并发服务端与客户端,也便于深入分析序列化协议、传输层和线程管理等底层机制;配套头文件与源文件一一对应,工程集成、维护与二次开发都很方便。目前已有542人学习下载,适合有一定C++基础、希望在Windows上使用或深度定制Thrift的开发者。 开头直接动手,不废话。先说结论:Windows + VS2010 编译 Apache Thrift,这件事完全可行,但过程绝不轻松。如果你是因为接手了一个老项目,业务代码锁定在 VS2010 的 C++ 工程里,又需要引入 Thrift 做 RPC 通信,那这篇文章就是为你准备的。
我前段时间刚把这套流程完整走了一遍,最后拿到了编译好的libthrift.lib、libthriftnb.lib以及完整的 C++ 源码,顺手解决了过程中遇到的一堆编译器和平台兼容性问题。这篇东西不是 Thrift 的官方文档翻译,也不是简单的步骤罗列,而是把我在 VS2010 这个"古董"环境下折腾 Thrift 时踩过的每一个坑、做出的每一个关键决策都拆开讲清楚。如果你用的也是 VS2010,或者 VS2013,这篇内容能帮你少走至少一整天的弯路。
1. 为什么要在 VS2010 环境下跟 Thrift 死磕:老项目的无奈与现实选择
1.1 谁会在 2025 年还用 VS2010 编 Thrift
先说个现实问题。Apache Thrift 目前最新版本(0.19.x 或者 0.20.x)的官方文档和构建脚本,早就把对老编译器的支持砍得差不多了。官方推荐的是 Visual Studio 2015 以上的环境,用 CMake 构建,配合 vcpkg 安装依赖。你去 GitHub 上看 Thrift 的源码,会发现cpp/目录下的 CMake 构建系统,最低也要求 MSVC 14.0(也就是 VS2015)。
但现实世界永远比开源社区的理想状态骨感得多。我遇到的情况是:某个运行了快十年的生产系统,它的核心业务模块是 VS2010 编译的,里面混着一堆只有老编译器才能通过的第三方库和平台相关的宏定义。为了给这个系统加一个新的数据交换通道,需要用 Thrift 定义接口、生成 C++ 代码,然后把这部分编译成静态库链接进老工程。系统不能重构,编译器也不能升级(升级 VS 意味着所有老第三方库都要重新验证一遍,风险不可控),那就只能在 VS2010 下想办法。
如果你的情况跟我一样,是被老工程绑架了,那 Thrift 的版本选择基本只有一个方向:用 0.9.x 系列或者 0.10.0。原因很简单,这两个版本是最后一批在thrift-0.9.3、thrift-0.10.0里自带contrib/目录下 vsproject 解决方案的版本。从 0.11.0 开始,官方把 VS 工程文件从源码树里移除了,彻底转向 CMake。当然,如果你愿意花时间做 CMake 生成 VS2010 工程的尝试,也不是完全不行,但生成的工程会引入大量 CMake 运行时依赖,而且对老的 Windows SDK 支持很差,实际意义不大。
1.2 0.10.0 这个版本为什么是最优解
我最终选用的是thrift-0.10.0,而不是更老的 0.9.3。核心原因有几个:
- 0.10.0 是
std::shared_ptr相关代码重构完成后的版本,生成的 C++ 代码在 VS2010 下编译比较顺畅(0.9.x 部分代码还在用boost::shared_ptr,在 VS2010 里虽然也能编译,但模板报错会多很多)。 - 0.10.0 修复了一些 SSL 连接和多线程方面的老 bug,代码稳定性比 0.9.3 好。
- 0.10.0 的
contrib/thrift.sln包含了libthrift、libthriftnb、thrift编译器(命令行工具)以及测试项目,一个解决方案全部搞定,不用自己额外搭建项目。
这个版本的源码编译产物,包含了你标题里提到的lib库和完整的 C++ 源码。所谓 "cpp源码",实际指的是两个层面的东西:一是 Thrift 编译器根据.thrift接口文件生成的那一堆xxx_types.h/cpp、xxx_constants.h/cpp文件;二是 Thrift 库自身的 C++ 实现源码(thrift-0.10.0/lib/cpp/目录下的所有.h/.cpp文件)。在 VS2010 下这个版本能完整编译通过,就已经解决掉 80% 的问题了。
2. 编译前的头号难题:Boost 和 OpenSSL 到底用哪个版本
2.1 确定 Boost 版本:VS2010 的编译器上限在哪
VS2010 的 C++ 编译器是 MSVC 10.0,它对 C++11 的支持停留在"半吊子"状态:auto、nullptr、右值引用这些能用,但constexpr、可变参数模板这些完全不行。这就直接决定了你选择 Boost 版本的上限。
经过实测(也参考了社区反馈),Boost 1.59.0 是 VS2010 能稳定编译的最后一个推荐版本。Boost 1.60 及以上的版本,部分头文件(尤其是boost/lexical_cast.hpp、boost/algorithm/string.hpp相关的)开始引入对constexpr和更现代特性的依赖,在 VS2010 下会报出一堆让人头皮发麻的模板编译错误。这不是 Boost 官方的错,是老编译器真的带不动新特性。
注意:这里说的"稳定"是指 Thrift 0.10.0 的代码在包含这些 Boost 头文件时不会出问题。你要是直接拿 VS2010 编译 Boost 1.59.0 本身,也得用
b2 toolset=msvc-10.0命令来编,而且只能编静态库(link=static),动态库在 VS2010 下坑更多。
2.2 OpenSSL 的版本坑:千万别用 1.1.0 以上的版本
Thrift 的TSocket和 SSL 相关代码(TSSLSocket.h/cpp)依赖 OpenSSL。在 Windows 下,编译libthrift时默认并不强制链接 OpenSSL(因为libthrift本身不带 SSL,libthriftnb才涉及非阻塞 IO 的 SSL),但如果你需要用到TSSLSocket,就必须把 OpenSSL 提前编译好。
这里有个特别容易踩的坑:OpenSSL 1.1.0 开始改变了 API 结构,把很多内部结构体改为不透明类型(opaque type),而 Thrift 0.10.0 的 SSL 代码是按照 1.0.x 的老 API 写的,直接用新版 OpenSSL 编译,会报incomplete type之类的错误。
所以我在这个项目里直接放弃了编译 OpenSSL 和libthriftnb,只编译libthrift静态库,把THRIFT_USE_OPENSSL相关的宏关掉。因为在我的场景里只需要同步的 RPC 调用,非阻塞 IO 完全用不上。如果你确实需要 SSL 或者非阻塞 IO,建议用OpenSSL 1.0.2u(1.0.2 系列的最终版)配合 VS2010 编译,这个是兼容性最好的组合。
2.3 依赖库的目录规划:省掉后面 90% 的路径坑
强烈建议在开始编译前,把目录结构固定下来。我用了下面这种方式:
C:\thrift-build\ ├── thrift-0.10.0\ # Thrift 源码解压目录 ├── boost_1_59_0\ # Boost 源码/库目录 │ ├── stage\lib\ # 编译好的 .lib 文件放这里 │ └── boost\ # 头文件目录 └── final\ ├── include\ # 最终拷贝的 Thrift 头文件(方便其他工程引用) ├── lib\ # 最终拷贝的 libthrift.lib └── thrift.exe # Thrift 编译器这样规划之后,VS2010 工程里配置附加包含目录和附加库目录的时候,只需要写一次绝对路径,后续其他同事在他们的电脑上也能按相同结构快速部署。我最开始偷懒用了解压后的默认目录,结果 Boost 库路径带boost_1_59_0这种带下划线的长目录名,在 VS2010 的某些对话框里偶尔会显示不全,虽然不影响实际编译,但看着确实闹心。
3. 完整编译流程:从源码到 lib 库和编译器的实操记录
3.1 使用 contrib 下的 VS2010 工程文件直接编译
Thrift 0.10.0 源码解压后,进入contrib/thrift.sln所在目录,你会看到一堆.vcxproj文件(这个解决方案已经是 VS2010 格式,不需要强制升级转换,双击直接打开就行)。解决方案结构大致是这样:
thrift.sln:主解决方案libthrift:生成libthrift.lib静态库libthriftnb:生成libthriftnb.lib(需要 libevent)thrift:生成thrift.exe命令行编译器- 其他测试相关的项目(可以忽略)
打开后,先在解决方案配置管理器里把平台切换到Win32,配置切换成Debug或Release(按你的需要)。然后检查libthrift项目的"附加包含目录"(项目属性 -> C/C++ -> 常规)里是否包含了 Boost 头文件路径。官方工程默认会写死一个路径,比如C:\boost_1_59_0或者C:\Program Files (x86)\boost\boost_1_59_0,你需要改成你自己的实际路径。
3.2 具体操作步骤:把编译从"波折"变"顺畅"
下面是我实际操作时的步骤,每一步都经过验证,但需要注意 VS2010 的表现可能跟你的环境有细微差异:
解压源码。确保
thrift-0.10.0的目录路径中不包含中文和空格。VS2010 对路径中的空格处理虽然比 VS6 好多了,但某些 NMake 规则还是会有问题。定位工程文件。打开
contrib/thrift.sln,如果弹出 VS 版本升级提示,不要升级,直接选择"打开"即可。升级会改变vcxproj格式,后续维护麻烦。配置包含目录。右键
libthrift项目 -> 属性 -> 配置属性 -> C/C++ -> 常规 -> 附加包含目录。把 Boost 的头文件目录加进去,例如:
C:\thrift-build\boost_1_59_0 C:\thrift-build\thrift-0.10.0\lib\cpp注意:lib\cpp目录必须加进去,因为 Thrift 的源码里大量使用了#include <thrift/config.h>这种相对仓库根目录的写法。你打开lib\cpp\thrift\Thrift.h就能看到它包含的是thrift/transport/...这样的路径,所以必须把lib\cpp作为根包含目录。
- 配置工程宏。在预处理定义里确认有
WIN32_LEAN_AND_MEAN、_WIN32_WINNT=0x0601(这个宏很重要,代表 Windows 7 平台支持,VS2010 默认的 SDK 支持到 0x0601)。如果缺少,加上。以下是我项目里最终使用的预处理定义:
WIN32;WIN32_LEAN_AND_MEAN;HAVE_CONFIG_H;_WIN32_WINNT=0x0601这里的关键是HAVE_CONFIG_H。没有这个宏,thrift/config.h不会被正确包含,编译时会出现一堆T_STRING、T_STOP等类型常量未定义的错误。
编译顺序。右键解决方案 -> 配置管理器 -> 只勾选
libthrift和thrift两个项目的"生成"。先单独编译libthrift,再编译thrift.exe。libthriftnb如果不需要先别勾选,那一堆 libevent 的报错会让人崩溃。拿到产物。在
contrib\libthrift\Release(或者对应的配置名称)目录下,就能找到libthrift.lib。在contrib\thrift\Release下找到thrift.exe。
3.3 基于官方工程的自定义补充:增加一个"纯净版"工程
官方contrib/thrift.sln里的libthrift项目有一个小问题:它会把lib/cpp下的所有.cpp文件都包含进项目。实际上其中有些文件依赖额外的第三方库(例如TSSLSocket.cpp依赖 OpenSSL,TPipe.cpp在非 Windows 平台才真正生效)。如果直接编译,会报无法打开openssl/ssl.h。
我当时用的是最简单粗暴但很有效的办法:从项目中排除掉TSSLSocket.cpp和TSSLServerSocket.cpp。右键这两个文件 -> 属性 -> 常规 -> "从生成中排除" 选择"是"。然后重新编译,一切正常。因为我的业务用不到 Thrift 的 SSL 传输层,所以排除了没有任何损失。如果对 SSL 有硬性要求,就必须先编译好 OpenSSL 1.0.2u,然后在工程属性里加上 OpenSSL 的头文件目录和库目录,并在链接器输入里加上libeay32.lib;ssleay32.lib。
我还把thrift编译器项目(生成thrift.exe的那个)的"字符集"属性从"使用 Unicode 字符集"改成了"使用多字节字符集",因为这个老的 thrift 编译器在生成代码时,内部对路径的处理用的是char*而不是wchar_t*,用 Unicode 字符集编译出来的thrift.exe在解析包含非 ASCII 字符的.thrift文件路径时容易出问题。
提示:如果你在编译
thrift.exe时遇到error C3861: 'strcasecmp': identifier not found这类错误,说明缺少_CRT_NONSTDC_NO_DEPRECATE宏。在项目属性 -> 预处理定义里加上即可。
4. 编译期和运行期的坑:我挨个踩过并完整的排查过程
4.1 坑一:config.h完全缺失,整个项目报错的地基问题
现象很吓人:编译刚开始,thrift/Thrift.h第一行#include <thrift/config.h>就报致命错误,说找不到这个文件。解决方案里明明有lib/cpp/thrift/config.h,为什么找不到?因为官方源码里根本没有这个文件,人家给的是config.hin(CMake 模板文件),需要在构建时通过 CMake 配置生成。
这个问题排查起来其实不难,但首次遇到很容易懵。我的做法是:进入lib/cpp/thrift目录,找到config.hin,用文本编辑器打开,把里面所有#cmakedefine开头的行都改成实际的定义值。当时我用的是这样的方法:
- 把
#cmakedefine HAVE_ARPA_INET_H 1删掉,因为 Windows 没有这个头文件。 - 把
#cmakedefine HAVE_NETINET_IN_H 1同样删掉。 - 把
#cmakedefine HAVE_STDINT_H 1改为#define HAVE_STDINT_H 1,VS2010 是有<stdint.h>的。 - 其他所有内容直接保留。
然后把config.hin重命名为config.h放在同一目录下。其实也可以直接复制一份config.hin改成config.h,然后逐个调整宏,更稳妥。
做完这一步之后,再编译,头文件层面的错误就几乎全部消失了。
4.2 坑二:boost/cstdint.hpp与 VS2010 的stdint.h冲突
编译到thrift/protocol/TProtocol.h附近时,可能出现类似error C2039: 'int64_t' : is not a member of 'std'的错误。这是 VS2010 的 C++ 标准库支持不全导致的。VS2010 的<cstdint>实际是不存在的(VS2012 才引入),所以 Thrift 的代码在 Windows 下依赖boost/cstdint.hpp来弥补。
但问题在于:如果你的包含路径里同时存在旧版 Boost 头文件和 Windows SDK 自带的stdint.h,可能会产生类型重定义冲突。我的解决办法是,在预处理定义里加上BOOST_HAS_STDINT_H,强制 Boost 使用标准库的stdint.h,避免它启用自己的boost/stdint.h里的类型定义。这个宏加上后,int64_t、uint64_t这些类型就稳定了。
4.3 坑三:链接时一堆LNK2019 无法解析的外部符号,指向thrift::transport::TTransportException
编译通过了,但链接阶段报了几十个 LNK2019,全是TTransportException、TProtocolException这类异常类的方法找不到实现。当时我的第一反应是:是不是库没编全?但我明明只编译了 libthrift 一个库。
后来排查发现,问题出在编译thrift.exe项目时,它链接的不是新编译的libthrift.lib,而是官方工程默认引用的一份单独的libthrift.lib。因为thrift.exe工程里用了编译时依赖和链接时依赖两套机制,如果你在"通用属性->框架和引用"里引用了libthrift项目,同时又在"链接器->输入->附加依赖项"里手动加了libthrift.lib,就会导致库被重复引用或者引用到旧路径。
我的处理方法是:去掉"框架和引用"里的项目引用,只保留"链接器->输入"里的绝对路径,指向我实际编译出来的libthrift.lib文件。这样链接就干净了。这个坑在 Visual Studio 的解决方案引用设置里很隐蔽,因为编译项目之间的依赖(Project Dependencies)和链接输入是两套系统。
4.4 坑四:运行时崩溃Unhandled exception at 0x... in thrift.exe: 0xC0000005: Access violation
编译器编译过了,拿一个简单的.thrift文件测试thrift.exe --gen cpp test.thrift,结果一运行就崩溃。排查链路如下:
- 先用最简单的
.thrift文件(只定义一个空 struct)测试,还是崩溃,说明不是语法解析问题。 - 再用 VS2010 自带的调试器附加进程,看崩溃堆栈,发现停在
std::string::assign附近的内部代码。 - 联想到 Debug 和 Release 混用的问题,检查发现我编译
thrift.exe用的是 Release 配置,但链接的libthrift.lib是 Debug 配置编出来的。Debug 的 STL 迭代器和内存布局与 Release 不同,链接在一起必然崩溃。
所以这里有个铁律:编译thrift.exe和libthrift.lib时,Debug/Release 配置必须保持一致。我重新把两个项目都切到 Release,清理解决方案,重新编译,一切正常。
这个坑在正常开发中太容易踩了:为了快速调试,自己敲命令
devenv thrift.sln /build Debug,结果后面切到 Release 链接,忘了混用的问题。建议每次切配置都先"清理解决方案"再重新"生成解决方案"。
4.5 坑五:运行thrift.exe生成的 C++ 代码在 VS2010 下还有一堆编译错误
thrift.exe生成出来的test_types.cpp,在自己的 VS2010 项目里引用时,可能报error C2244: 'apache::thrift::to_string' : unable to match function definition to an existing declaration。
这个错误跟 Thrift 库本身无关,是生成代码里用了一些 VS2010 编译器支持不完善的特性,尤其是针对std::to_string的适配。Thrift 0.10.0 里已经有了自己的to_string辅助函数(在thrift/TOString.h),但生成的代码在某些重载场景还是会把编译器搞晕。
我的解决方法是:在用到生成代码的 VS2010 工程里,强制定义宏_VARIADIC_MAX=10并加上#define __FUNCTION__ __FUNCTION__这类无奈但必要的兼容宏。另外,如果生成代码里包含了<tuple>或者std::function相关的内容,那就得检查是不是用了 C++11 的语法,必要时手动修改生成代码(虽然是不得已的办法,但老项目里很常见)。
5. 编译完成后:验证产物、集成到老工程以及后续的建议
5.1 验证thrift.exe和libthrift.lib的有效性
编译不是终点,验证才是。我的验证方式是分三步走:
命令行测试编译器。执行
thrift.exe --gen cpp test.thrift,如果没有任何报错,并且生成了test_types.h、test_types.cpp、test_constants.h、test_constants.cpp四个文件,说明编译器基本正常。编译生成代码。快速新建一个空的 VS2010 控制台程序,把上面生成的四个文件加进去,并在主函数里创建一个
TestStruct对象,调用TestStruct().write相关接口(哪怕不真正运行,只要编译通过就行),以此确认生成代码与库的兼容性。写一个最小化的客户端/服务端连通测试。在 VS2010 下分别编译一个客户端和一个服务端(可以用 Thrift 自带的例子改),通过
TSocket和TBufferedTransport方式做一次字符串交换。这个测试能同时验证libthrift.lib的链接、运行时的网络通信、以及生成代码的序列化反序列化是否正常。
5.2 集成到老工程时,链接参数和部署要点
编译好的libthrift.lib是静态库,链接到老工程时要注意以下几点:
- 运行时库设置要一致。如果你在编译 libthrift 时用的是
/MD(多线程 DLL),老工程也必须是/MD。如果 lib 用/MT,老工程用/MD,链接时会出现LNK2038 不匹配错误。Thrift 官方 contrib 工程的默认设置是/MD,我建议直接保持这个默认值,因为大多数 VS2010 工程默认就是/MD。 - 附加依赖项要加全。
libthrift.lib本身的附加依赖项包括ws2_32.lib(Winsock 库)和kernel32.lib。在对方工程里,ws2_32.lib经常容易被漏掉,导致链接时出现unresolved external symbol __imp__send之类的问题。 - 部署:由于是静态链接,最终生成的
.exe不需要额外携带 Thrift 的 DLL,但如果你用了/MD,仍需确保目标机器上有对应版本的 VC++ 运行库(VS2010 对应的是 VC++ 10 运行库)。
5.3 老系统维护角度的一点个人建议
这套编译方案虽然能跑通,但毕竟属于"老系统上强行嫁接新技术",维护上有一些坑是绕不开的。我的个人建议是:
- 保持 Thrift IDL 文件独立管理。把
.thrift文件放在一个独立目录,每次修改后单独用thrift.exe重新生成代码,生成代码不要手动改(改过很难同步)。 - 不要随意升级 Thrift 版本。在 VS2010 这个环境下,
0.10.0是我验证过的稳定版本,升级到 0.11 及以上大概率会碰到编译器不支持的问题。既然系统整体锁定在老环境,Thrift 也跟着锁定,这是最稳的。 - 把编译好的库纳入版本控制。既然不是每个人都能轻松在本机编译出
libthrift.lib,直接把最终的lib、include和thrift.exe作为一个"基础工具包"提交到代码仓库,让其他同事直接引用就行。这比让每个人折腾 Boost 和 OpenSSL 靠谱得多。
最后,从实际操作来看,编译这套 Thrift 虽然折腾,但它的价值在于把老系统接入现代 RPC 协议的大门给踹开了。只要第一步走通,后续的 IDL 定义和业务开发,就跟正常 Thrift 开发没有太大区别了。
本文还有配套的精品资源,点击获取