Thrift之代码生成器Compiler原理及源码详细解析2
我的新浪微博:http://weibo.com/freshairbrucewoo。
欢迎大家相互交流,共同提高技术。
2 t_generator类和t_generator_registry类
这个两个类的主要功能就是为生成所有语言的代码提供基础信息和提供具体代码生成器对象,上面就是调用这个两个类的方法来生成具体语言的代码生成器对象和执行生成代码的功能函数。下面主要分析两个函数的功能,一个是t_generator_registry类的get_generator函数,这个是一个静态的函数可以直接通过类调用;另一个是t_generator类的generate_program函数。
(1)t_generator_registry类的get_generator函数
这个函数有两个参数,一个是表示程序的对象program,另一个是语言字符串参数(包括代表语言的简短字符串和可选项的组合,有的没有)。函数首先解析语言字符串参数,参数字符串中是这样组织的:在冒号(:)之前是代表语言的字符串,冒号之后是可选项的参数,每一个可选项参数用逗号(,)分割,每一个可选项参数都是键值对并且键和值是用等号(=)分割。按照上面的字符串格式解析各个参数部分就可以了,可选项参数用map来保存键值对,代码实现如下:
1 string::size_type colon = options.find(':');
2
3 string language = options.substr(0, colon);
4
5 map<string, string> parsed_options;
6
7 if (colon != string::npos) {
8
9 string::size_type pos = colon+1;
10
11 while (pos != string::npos && pos < options.size()) {
12
13 string::size_type next_pos = options.find(',', pos);
14
15 string option = options.substr(pos, next_pos-pos);
16
17 pos = ((next_pos == string::npos) ? next_pos : next_pos+1);
18
19 string::size_type separator = option.find('=');
20
21 string key, value;
22
23 if (separator == string::npos) {
24
25 key = option;
26
27 value = "";
28
29 } else {
30
31 key = option.substr(0, separator);
32
33 value = option.substr(separator+1);
34
35 }
36
37 parsed_options[key] = value;
38
39 }
40
41 }
然后调用get_generator_map函数得到一个代表语言字符串和产生这种语言生成器对象的工厂对象的map对象:gen_map_t& the_map = get_generator_map(); gen_map_t的定义如下:
1 typedef std::map<std::string, t_generator_factory*> gen_map_t;
get_generator_map函数只有两句代码,一个是定义一个静态局部变量并初始化(因为静态局部变量必须并初始化并且只有第一次会执行初始化,因为不初始化链接程序的时候会报错),第二句就是返回这个静态局部变量给调用者,代码如下:
1 static gen_map_t* the_map = new gen_map_t();
2
3 return *the_map;
然后在这个map对象中找到对应语言的工厂对象,然后用这个工厂对象生产一个这种语言的代码生成器对象并返回给调用者,代码如下所示:
1 gen_map_t::iterator iter = the_map.find(language);
2
3 return iter->second->get_generator(program, parsed_options, options);
本函数的功能已经分析完毕,但是还存在着两个问题(或是困难)。一个是最后一条返回一句是根据具体的语言来使用具体语言生产器的工厂对象生产代码生成器,具体又是怎么生成的了?第二个就是从main函数执行到现在还没有发现在哪儿为get_generator_map函数里定义的静态局部变量添加过任何键值对,那么我们查找具体语言必定会失败,那么会返回一个NULL给调用者,那么程序就会执行不下去了,但是程序确实能够完完整整的执行下去,这个问题困扰了我好一会儿。下面就这两个问题继续分析相关代码并且解决问题。
第一个应该不算是问题,但是必须要解决第二个问题以后才能够解释,因为没有解决第二个问题,那么根本就不会执行到最后一条返回语句这儿来,所以我先解决第二个问题。
第二个问题分析和解决思路如下:
我们通常认为main函数是程序的入口函数,那么所以程序的执行都是从main函数开始的,所以我也选择从main函数开始分析这部分的代码,根据程序的执行流程阅读和分析代码是我一贯的思路。但是这种情况在C++里面有例外,记得我在学习MFC的时候,分析MFC执行过程就发现一个问题,那就是全局变量的初始化是在main函数开始之前的,也就是说全局类对象的构造函数也是在main执行之前执行的。由于我反复从main开始一直详细的阅读每一行代码,所以可以确定确实没有在执行的过程中初始化the_map静态局部变量,所以唯一的可能就是在main函数开始之前已经初始化好了。根据这一点思路自己开始着手查找初始化the_map的代码,发现t_generator_registry类的register_generator函数为the_map添加键值对了,这个函数定义如下:
1 void t_generator_registry::register_generator(t_generator_factory* factory) {
2
3 gen_map_t& the_map = get_generator_map();
4
5 if (the_map.find(factory->get_short_name()) != the_map.end()) {
6
7 failure("Duplicate generators for language \"%s\"!\n", factory->get_short_name().c_str());
8
9 }
10
11 the_map[factory->get_short_name()] = factory;
12
13 }
这个函数也首先调用get_generator_map函数得到那个静态局部变量,然后查找要注册的工程是否已经在the_map中存在,如果存在就提示失败信息,否则就把工厂的名字和工厂对象作为键值对添加到the_map中。
虽然找到了为the_map添加键值对的地方,但是还没有找到调用这个注册工厂函数的地方,所以继续在代码中搜索调用这个函数的地方。整个代码就只有一处调用了这个函数,而且是在一个类的构造函数中,代码如下:
1 t_generator_factory::t_generator_factory(const std::string& short_name, const std::string& long_name,
2
3 const std::string& documentation) : short_name_(short_name)
4
5 , long_name_(long_name) , documentation_(documentation)
6
7 {
8
9 t_generator_registry::register_generator(this);
10
11 }
t_generator_factory类是所有生产代码生产器对象工厂的基类,每一种具体的语言都有自己的代码生成器类和生产这种类的工厂类,上面的代码是它的构造函数,功能就是把自己注册到the_map中。看到这里是否有一种逐渐清晰的感觉,但是总是感觉还有少点什么,就是这个构造函数被调用也必须有这个类的对象被定义或其子类的对象被定义。于是我又开始搜索哪些类是从这个类继承的,发现两处很重要的代码,一处如下:
1 template <typename generator>
2
3 class t_generator_factory_impl : public t_generator_factory {
4
5 public:
6
7 t_generator_factory_impl(const std::string& short_name, const std::string& long_name,
8
9 const std::string& documentation) : t_generator_factory(short_name, long_name, documentation)
10
11 {}
12
13 virtual t_generator* get_generator(t_program* program,
14
15 const std::map<std::string, std::string>& parsed_options, const std::string& option_string) {
16
17 return new generator(program, parsed_options, option_string);
18
19 }
20
21 ……//此处省略了一些代码
22
23 };
t_generator_factory_impl类继承了t_generator_factory类,而且在构造函数的时候也调用了父类的构造函数,因为是带参数的构造函数所以必须手动调用父类的构造函数。这个类是一个模板类,模板参数就是一个代码生成器类,所以函数get_generator就能够根据这个模板参数生成new一个对应语言的代码生成器对象了。这里就把上面提到的第一个问题也解决了,每一个工厂类把自己注册到the_map,然后使用者通过代表语言的键(key)在the_map找到对应的工厂对象,然后调用get_generator函数就生成具体的代码生成器对象了,这就是第一个问题提到的最后一句返回语句的代码执行情况。
但是还是没有看到定义具体的工厂对象呀,那么还需要看下面一处的代码:
1 #define THRIFT_REGISTER_GENERATOR(language, long_name, doc) \
2
3 class t_##language##_generator_factory_impl \
4
5 : public t_generator_factory_impl<t_##language##_generator> \
6
7 { \
8
9 public: \
10
11 t_##language##_generator_factory_impl() \
12
13 : t_generator_factory_impl<t_##language##_generator>( \
14
15 #language, long_name, doc) \
16
17 {} \
18
19 }; \
20
21 static t_##language##_generator_factory_impl _registerer;
这是一个宏定义,它根据参数language定义一个生产具体语言的代码生成器的工厂类,并从t_generator_factory_impl类继承,传递的模板参数也是对应语言的代码生成器类,构造函数同样调用了父类的构造函数;最后还定义了一个对应的静态的类全局变量(千呼万唤始出来,终于找到定义类的全局变量了)。但是还是存在同样的问题就是定义了宏函数还是需要调用才执行吧,所以就在代码中搜索调用了这个宏函数的代码,最终发现这个每一个具体的语言代码生成器的文件都调用了一次,如下面是C++的文件t_cpp_generator.cc中调用的代码:
1 THRIFT_REGISTER_GENERATOR(cpp, "C++",
2
3 " pure_enums: Generate pure enums instead of wrapper classes.\n"
4
5 " dense: Generate type specifications for the dense protocol.\n"
6
7 " include_prefix: Use full include paths in generated files.\n"
8
9 )
其他语言的代码生成器类的定义文件中都有类似的调用,这样每一个语言生成器对象的生产工厂就被注册到the_map中了,由此问题得到解决。
(2)t_generator类的generate_program函数
这个函数是生成具体语言代码的顶层函数,它会调用子类定义的各个子函数来做具体代码的生成过程,后面会详细解析C++、java和python代码生成的过程。
首先调用代码生成器的初始化函数来初始化代码生成器,然后依次调用各种基本数据类型和服务的生成函数来生成相应的代码,最后关闭代码生成器。代码实现如下:
1 init_generator();
2
3 vector<t_enum*> enums = program_->get_enums();
4
5 vector<t_enum*>::iterator en_iter;
6
7 for (en_iter = enums.begin(); en_iter != enums.end(); ++en_iter) {
8
9 generate_enum(*en_iter);
10
11 }
12
13 vector<t_typedef*> typedefs = program_->get_typedefs();
14
15 vector<t_typedef*>::iterator td_iter;
16
17 for (td_iter = typedefs.begin(); td_iter != typedefs.end(); ++td_iter) {
18
19 generate_typedef(*td_iter);
20
21 }
22
23 vector<t_const*> consts = program_->get_consts();
24
25 generate_consts(consts);
26
27 vector<t_struct*> objects = program_->get_objects();
28
29 vector<t_struct*>::iterator o_iter;
30
31 for (o_iter = objects.begin(); o_iter != objects.end(); ++o_iter) {
32
33 if ((*o_iter)->is_xception()) {
34
35 generate_xception(*o_iter);
36
37 } else {
38
39 generate_struct(*o_iter);
40
41 }
42
43 }
44
45 vector<t_service*> services = program_->get_services();
46
47 vector<t_service*>::iterator sv_iter;
48
49 for (sv_iter = services.begin(); sv_iter != services.end(); ++sv_iter) {
50
51 service_name_ = get_service_name(*sv_iter);
52
53 generate_service(*sv_iter);
54
55 }
56
57 close_generator();
此函数使用的是词法和语法分析结果的一些符号,这些符号都保持在t_program对象的对于数据结构里面,所以上面的函数依次从t_program对象中取得各种数据类型的符号和服务的符号,并依次生成。
(3)t_generator类的其它功能简介
这个类是所有具体语言代码生成器的共同基类,所以定义了很多各种语言代码生成需要的共同功能,例如生成代码的格式控制、命名空间的有效性检查、驼峰标识符和下划线标识符的相互转换等等。这些功能比较简单,需要可以直接查看源代码。
Thrift之代码生成器Compiler原理及源码详细解析2的更多相关文章
- Thrift之代码生成器Compiler原理及源码详细解析1
我的新浪微博:http://weibo.com/freshairbrucewoo. 欢迎大家相互交流,共同提高技术. 又很久没有写博客了,最近忙着研究GlusterFS,本来周末打算写几篇博客的,但是 ...
- Thrift之TProcess类体系原理及源码详细解析
我的新浪微博:http://weibo.com/freshairbrucewoo. 欢迎大家相互交流,共同提高技术. 之前对Thrift自动生成代码的实现细节做了详细的分析,下面进行处理层的实现做详细 ...
- thrift之TTransport类体系原理及源码详细解析1-类结构和抽象基类
本章主要介绍Thrift的传输层功能的实现,传输的方式多种多样,可以采用压缩.分帧等,而这些功能的实现都是相互独立,和上一章介绍的协议类实现方式比较雷同,还是先看看这部分的类关系图,如下: 由上面的类 ...
- 并发编程(十五)——定时器 ScheduledThreadPoolExecutor 实现原理与源码深度解析
在上一篇线程池的文章<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中从ThreadPoolExecutor源码分析了其运行机制.限于篇幅,留下了Scheduled ...
- 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)
在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...
- JVM CPU Profiler技术原理及源码深度解析
研发人员在遇到线上报警或需要优化系统性能时,常常需要分析程序运行行为和性能瓶颈.Profiling技术是一种在应用运行时收集程序相关信息的动态分析手段,常用的JVM Profiler可以从多个方面对程 ...
- 并发编程(十三)—— Java 线程池 实现原理与源码深度解析 之 Executors(三)
前两篇文章讲了线程池的源码分析,再来看这篇文章就比较简单了, 本文主要讲解 Executors 这个工具类,看看长江创建线程池的几种方法. newFixedThreadPool 生成一个固定大小的线程 ...
- 并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)
史上最清晰的线程池源码分析 鼎鼎大名的线程池.不需要多说!!!!! 这篇博客深入分析 Java 中线程池的实现. 总览 下图是 java 线程池几个相关类的继承结构: 先简单说说这个继承结构,E ...
- Vue源码详细解析:transclude,compile,link,依赖,批处理...一网打尽,全解析!
用了Vue很久了,最近决定系统性的看看Vue的源码,相信看源码的同学不在少数,但是看的时候却发现挺有难度,Vue虽然足够精简,但是怎么说现在也有10k行的代码量了,深入进去逐行查看的时候感觉内容庞杂并 ...
随机推荐
- visual c++ 2013进行MySQL编程(ODBC) -- (一) 套装安装
最近写了有些技术类文章了,只因为最近研究多了些东西,有一些项目用到了,所以自己记录一下,怕自己忘记,如果有哪位同学有自己的见解,可以邮件或者回复,技术类的探讨,不管对否,都是欢迎的. 操作之前,必须安 ...
- 工作总结 mvc外键 public virtual SysUser TransferUser { get; set; } 必须要加 virtual 否则 TransferUser 值为null 还要加[ForeignKey("TransferUser")] Bind 和 ScaffoldColumn(转)
[Table("T_SYS_TRANSFERUSER")] public class SysTransferUser : DbSetBase { [ForeignKey(" ...
- Mysql导出大量数据
outfile 导出文件 select name from t1 into outfile "/tmp/test.txt" infile 导入文件 导入到表t1中的name ...
- 微博达人硅谷之歌:Testin云測移动搜索性能測试非常是让人信服
微博达人硅谷之歌:Testin云測移动搜索性能測试非常是让人信服 2014/10/08 · Testin · 开发人员訪谈 2013年11月1日,谷歌运行董事长施密特(Eric Emerson Sch ...
- Memory-mapped I/O vs port-mapped I/O
关于MMIO和PIO,我看到的解释最清楚的文章,原文在这里:Memory-mapped I/O vs port-mapped I/O - 2015 Microprocessors normally u ...
- NYOJ 905 卡片游戏
卡片游戏 时间限制:1000 ms | 内存限制:65535 KB 难度:1 描写叙述 小明近期宅在家里无聊.于是他发明了一种有趣的游戏.游戏道具是N张叠在一起的卡片,每张卡片上都有一个数字,数字 ...
- 【转】Android IDA 动态调试最完善攻略,跨过各种坑
前提条件和运行环境一定要写清楚,不然会有很多坑,坑死人. (1)IDA 是最新的7.0版本 (2) JDB 使用Java安装目录下的 (3)系统是win10 使用命令窗口时有很大的差别 (4)手机是 ...
- php 批量删除数据
php 批量删除数据 :比如我们在看邮箱文件的时候,积攒了一段时间以后,看到有些文件没有用了 这时候我们就会想到把这些 没用的文件删除,这时候就用到了批量删除数据的功能,这里我是用了数据库原有的一个表 ...
- 2016/07/11 PHP接口的介绍与实现
接口定义了实现某种服务的一般规范,声明了所需的函数和常量,但不指定如何实现.之所以不给出实现的细节,是因为不同的实体可能需要用不同的方式来实现公共的方法定义.关键是要建立必须实现的一组一般原则 ...
- 51 NOD 1753 相似子串 字符串hash
1735 相似子串 基准时间限制:5 秒 空间限制:131072 KB 分值: 80 两个字符串相似定义为:1.两个字符串长度相等2.两个字符串对应位置上有且仅有至多一个位置所对应的字符不 ...