Thrift之代碼生成器Compiler原理及源碼詳細(xì)解析3
我的新浪微博:http://weibo.com/freshairbrucewoo。
歡迎大家相互交流,共同提高技術(shù)。
3 生成C++語(yǔ)言代碼的代碼詳解
這個(gè)功能是由t_cpp_generator類實(shí)現(xiàn)(在文件t_cpp_generator.cc定義和實(shí)現(xiàn)),直接繼承至t_oop_generator類(這個(gè)類是所有面向?qū)ο笳Z(yǔ)言生成器類的直接基類,封裝了面向?qū)ο笳Z(yǔ)言生成器共有的特征與行為),而t_oop_generator又從t_generator繼承(上面已經(jīng)介紹),下面詳細(xì)分析這個(gè)類是怎樣生成C++語(yǔ)言的代碼文件的。這個(gè)還有從上面介紹的generate_program函數(shù)開(kāi)始說(shuō)起,因?yàn)檫@個(gè)函數(shù)才是控制整個(gè)代碼生成的總樞紐。
首先執(zhí)行的是構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)做了一些最基本的初始化,一個(gè)是傳遞擁有生成代碼的符號(hào)資源的t_program對(duì)象到父類,第二個(gè)功能就是根據(jù)可選項(xiàng)參數(shù)初始化一些bool變量,以便后面根據(jù)這些bool變量做相應(yīng)的處理,代碼很簡(jiǎn)單就不列出來(lái)了,下面用一個(gè)表格說(shuō)明各個(gè)bool變量的作用(或功能)。
|
gen_pure_enums_ |
是否生成純凈的枚舉類型,而不是采用類包裝的形式 |
|
gen_dense_ |
是否應(yīng)該為TDenseProtocol生成本地反射的元數(shù)據(jù)。 |
|
gen_templates_ |
是否要生成模板化的讀/寫方法 |
|
use_include_prefix_ |
是否應(yīng)該為了thrift生成的其他頭文件在#include中使用前綴路徑 |
|
gen_cob_style_ |
是否應(yīng)該生成繼承擴(kuò)展功能類(主要是異步) |
|
gen_no_client_completion_ |
是否應(yīng)該省略客戶端類調(diào)用completion__() |
構(gòu)造函數(shù)只是做了最基本的初始化,更詳細(xì)的初始化是上面介紹的代碼生成器初始化函數(shù)init_generator,那我們看看C++代碼生成器是怎么詳細(xì)初始化的,都做了一些什么樣的工作和實(shí)現(xiàn)了一些什么的功能。我們分步驟介紹這一個(gè)函數(shù):
第一步:制作代碼文件的輸出目錄:MKDIR(get_out_dir().c_str());MKDIR是一個(gè)宏函數(shù),調(diào)用了mkdir來(lái)創(chuàng)建目錄;
第二部:創(chuàng)建代碼頭文件和實(shí)現(xiàn)文件,如果需要生成模板化的讀和寫的方法還會(huì)創(chuàng)建一個(gè)文件單獨(dú)實(shí)現(xiàn),代碼如下:
1 string f_types_name = get_out_dir()+program_name_+"_types.h"; 2 3 f_types_.open(f_types_name.c_str()); 4 5 string f_types_impl_name = get_out_dir()+program_name_+"_types.cpp"; 6 7 f_types_impl_.open(f_types_impl_name.c_str()); 8 9 if (gen_templates_) { 10 11 string f_types_tcc_name = get_out_dir()+program_name_+"_types.tcc"; 12 13 f_types_tcc_.open(f_types_tcc_name.c_str()); 14 15 }
這里需要說(shuō)明幾個(gè)用于輸出流的成員變量,之所以定義成員變量是因?yàn)楹芏嗪瘮?shù)會(huì)用到,這樣就不用用參數(shù)來(lái)傳遞它們了,它們定義和說(shuō)明如下:
1 std::ofstream f_types_;//專門用于類型聲明的輸出流,也就是頭文件(.h文件) 2 3 std::ofstream f_types_impl_;//專門用于類型實(shí)現(xiàn)的輸出流,也就是實(shí)現(xiàn)文件(.cpp文件) 4 5 std::ofstream f_types_tcc_;//專門用于模板化的讀和寫方法實(shí)現(xiàn)的輸出流 6 7 std::ofstream f_header_;//專門用于服務(wù)聲明生成的輸出流 8 9 std::ofstream f_service_;//專門用于服務(wù)實(shí)現(xiàn)生成的輸出流 10 11 std::ofstream f_service_tcc_;//專門用于模板的服務(wù)的輸出流
第三步:為每個(gè)文件打印頭部注釋,注釋的作用就是說(shuō)明這個(gè)文件是由Thrift自動(dòng)生成的,代碼如下:
1 f_types_ << autogen_comment(); 2 3 f_types_impl_ << autogen_comment(); 4 5 f_types_tcc_ << autogen_comment();
第四步:開(kāi)始ifndef
第五步:包含各種頭文件
第六步:打開(kāi)命名空間,生成的代碼都是在一個(gè)命令空間里面的。
以上步驟的功能都比較簡(jiǎn)單,主要就是注意輸出格式和邏輯處理。通過(guò)這些功能基本內(nèi)容都做好了,下面就是真正開(kāi)始生成具體類型和服務(wù)的時(shí)候了,每一種數(shù)據(jù)類型都由一個(gè)單獨(dú)的函數(shù)來(lái)負(fù)責(zé)生成為代碼。
(1)枚舉類型生成函數(shù)generate_enum
首先在頭文件中生成定義枚舉類型的代碼,具體的過(guò)程就是得到枚舉的所有常量值和枚舉類型的名稱,然后根據(jù)C++定義枚舉類型的語(yǔ)法輸出代碼到頭文件,輸出過(guò)程中根據(jù)是否需要用類來(lái)包裝而所有不同,同時(shí)生成的代碼也需要格式控制。具體實(shí)現(xiàn)如下:
1 vector<t_enum_value*> constants = tenum->get_constants(); 2 3 std::string enum_name = tenum->get_name(); 4 5 if (!gen_pure_enums_) { 6 7 enum_name = "type"; 8 9 f_types_ << indent() << "struct " << tenum->get_name() << " {" << endl; 10 11 indent_up(); 12 13 } 14 15 f_types_ << indent() << "enum " << enum_name; 16 17 generate_enum_constant_list(f_types_, constants, "", "", true); 18 19 if (!gen_pure_enums_) { 20 21 indent_down(); 22 23 f_types_ << "};" << endl; 24 25 } 26 27 f_types_ << endl;
接著在后面在實(shí)現(xiàn)文件中定義一個(gè)整型數(shù)組和一個(gè)字符的數(shù)組并用定義的枚舉類型的常量值來(lái)初始化這兩個(gè)數(shù)組,后然在說(shuō)這兩個(gè)數(shù)組的值初始化一個(gè)map,其實(shí)這么做的目的就是為了測(cè)試這個(gè)枚舉類型定義是否正確。
最后調(diào)用函數(shù)generate_local_reflection決定是否為TDenseProtocol協(xié)議生成對(duì)應(yīng)類型的本地反射類型。這個(gè)函數(shù)功能比較復(fù)雜,后面單獨(dú)詳細(xì)講解。
(2)類型定義生成函數(shù)generate_typedef
此函數(shù)功能簡(jiǎn)單,就是在頭文件中生成一個(gè)typedef的定義,就只有一句實(shí)現(xiàn):
1 f_types_ << indent() << "typedef " << type_name(ttypedef->get_type(), true) << " " 2 3 << ttypedef->get_symbolic() << ";" << endl << endl;
(3)常量類型生成函數(shù)generate_consts
常量類型的實(shí)現(xiàn)是采用一個(gè)類來(lái)包裝所有的常量并且使用單獨(dú)的文件來(lái)實(shí)現(xiàn),所有首先創(chuàng)建常量類型定義頭文件和實(shí)現(xiàn)文件,代碼如下:
1 string f_consts_name = get_out_dir()+program_name_+"_constants.h"; 2 3 ofstream f_consts; 4 5 f_consts.open(f_consts_name.c_str()); 6 7 string f_consts_impl_name = get_out_dir()+program_name_+"_constants.cpp"; 8 9 ofstream f_consts_impl; 10 11 f_consts_impl.open(f_consts_impl_name.c_str());
接著按照就開(kāi)始按照類的定義格式在頭文件中生成定義類的代碼并在實(shí)現(xiàn)文件中定義這個(gè)類的常量類型;在這個(gè)類的構(gòu)造函數(shù)中給定義的數(shù)據(jù)類型賦值:
1 f_consts_impl << "const " << program_name_ << "Constants g_" << program_name_ << "_constants;" << endl << 2 3 endl << program_name_ << "Constants::" << program_name_ << "Constants() {" << endl; 4 5 indent_up(); 6 7 for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { 8 9 print_const_value(f_consts_impl, (*c_iter)->get_name(), (*c_iter)->get_type(), (*c_iter)->get_value()); 10 11 } 12 13 indent_down(); 14 15 indent(f_consts_impl) << "}" << endl;
其中調(diào)用了print_const_value函數(shù)來(lái)根據(jù)數(shù)據(jù)類型來(lái)賦值,這些定義在類中的成員變量本身不是常量類型,只是在實(shí)現(xiàn)文件中定義了一個(gè)類的全局常量對(duì)象,在頭文件中聲明,以便其他地方可以被使用。
(4)異常類型生成函數(shù)generate_xception
這個(gè)函數(shù)其實(shí)調(diào)用下面需要詳細(xì)分析的一個(gè)函數(shù)實(shí)現(xiàn)的,就是generate_struct函數(shù),因?yàn)楫惓R彩峭ㄟ^(guò)結(jié)構(gòu)體來(lái)定義和實(shí)現(xiàn)的。不過(guò)C++語(yǔ)言生成器中也自己實(shí)現(xiàn)了這個(gè)函數(shù),不過(guò)它是調(diào)用generate_cpp_struct函數(shù)實(shí)現(xiàn),C++的generate_struct函數(shù)也是調(diào)用這個(gè)函數(shù)實(shí)現(xiàn),只是傳遞一個(gè)bool變量來(lái)區(qū)分是否是異常類型,具體的實(shí)現(xiàn)在分析generate_struct函數(shù)時(shí)一起詳細(xì)分析了,因?yàn)樗鼈兊幕緦?shí)現(xiàn)功能都是相同的。
(5)結(jié)構(gòu)體類型生成函數(shù)generate_struct
上面已經(jīng)說(shuō)了這個(gè)函數(shù)也是調(diào)用generate_cpp_struct函數(shù)實(shí)現(xiàn),也就是說(shuō)異常類型和結(jié)構(gòu)體類型都是用同樣的流程實(shí)現(xiàn)的,它們都是定義為一個(gè)類,只是異常都從TException繼承,而一般的結(jié)構(gòu)體沒(méi)有。首先調(diào)用函數(shù)generate_struct_definition在頭文件中生成定義類的代碼,這個(gè)過(guò)程如下:
第一步:得到所有的成員變量;
第二步:根據(jù)是否有可選成員決定是否定義一個(gè)結(jié)構(gòu)體_XXX_isset,這個(gè)結(jié)果主要針對(duì)需要定義的類的可選成員而定義一些bool變量,來(lái)標(biāo)識(shí)這些可選成員變量是否存在。設(shè)計(jì)這個(gè)功能的目的是為了靈活控制數(shù)據(jù)傳輸?shù)慕Y(jié)構(gòu);
第三步:開(kāi)始生成定義類(IDL文件中定義的struct在C++都是用class來(lái)實(shí)現(xiàn))的代碼,生成的代碼主要包括默認(rèn)的構(gòu)造函數(shù)、析構(gòu)函數(shù)、各個(gè)字段、比較函數(shù)(等于、不等于和小于)等;
第四步:最后一步生成一個(gè)模板的讀和寫數(shù)據(jù)的函數(shù)的聲明,模板參數(shù)是協(xié)議類型,實(shí)現(xiàn)代碼如下:
1 if (read) {//讀數(shù)據(jù)的模板函數(shù) 2 3 if (gen_templates_) { 4 5 out <<indent() << "template <class Protocol_>" << endl << 6 7 indent() << "uint32_t read(Protocol_* iprot);" << endl; 8 9 } else { 10 11 out << indent() << "uint32_t read(" << "::apache::thrift::protocol::TProtocol* iprot);" << endl; 12 13 } 14 15 } 16 17 if (write) {//寫數(shù)據(jù)的模板函數(shù) 18 19 if (gen_templates_) { 20 21 out << indent() << "template <class Protocol_>" << endl << 22 23 indent() << "uint32_t write(Protocol_* oprot) const;" << endl; 24 25 } else { 26 27 out << indent() << "uint32_t write(" << "::apache::thrift::protocol::TProtocol* oprot) const;" << endl; 28 29 } 30 31 }
然后調(diào)用函數(shù)generate_struct_fingerprint在實(shí)現(xiàn)文件中初始化兩個(gè)靜態(tài)變量,一個(gè)是字符串,一個(gè)是8位的整型數(shù)組,這兩個(gè)變量都是用來(lái)唯一的標(biāo)識(shí)一個(gè)類。這個(gè)歌標(biāo)識(shí)符的作用就是用于生成本地的反射類型,當(dāng)使用TDenseProtocol協(xié)議傳輸數(shù)據(jù)時(shí)會(huì)用到。
接著兩次調(diào)用generate_local_reflection函數(shù)分別來(lái)聲明和定義用于類的本地反射的類型,調(diào)用generate_local_reflection_pointer函數(shù)來(lái)生成一個(gè)類的靜態(tài)指針的本地反射類型。
最后分別調(diào)用函數(shù)generate_struct_reader和generate_struct_writer實(shí)現(xiàn)數(shù)據(jù)讀和寫函數(shù)。到此整個(gè)IDL定義的struct類型生成為C++的代碼就完成了。
(6)服務(wù)類型生成函數(shù)generate_service
這個(gè)函數(shù)的功能是最復(fù)雜的,它會(huì)做很多的工作(分別調(diào)用其它函數(shù)來(lái)實(shí)現(xiàn)),也會(huì)生成單獨(dú)的頭文件和實(shí)現(xiàn)文件。生成頭文件的代碼如下:
1 string f_header_name = get_out_dir()+svcname+".h"; 2 3 f_header_.open(f_header_name.c_str());
下面就開(kāi)始在頭文件中生成一些包含頭文件的代碼。
生成實(shí)現(xiàn)文件的代碼:
1 string f_service_name = get_out_dir()+svcname+".cpp"; 2 3 f_service_.open(f_service_name.c_str());
后面也是生成一些包含頭文件的代碼。接著就開(kāi)始生成正在的各種實(shí)現(xiàn)這個(gè)服務(wù)的代碼了,如下:
1 generate_service_interface(tservice, "");//生成服務(wù)的接口類(在C++為抽象類) 2 3 generate_service_null(tservice, "");//生成一個(gè)空實(shí)現(xiàn)服務(wù)接口類的類 4 5 generate_service_helpers(tservice);//生成一些幫助類,如參數(shù)類、返回結(jié)果類等 6 7 generate_service_client(tservice, "");//生成一個(gè)客戶類 8 9 generate_service_processor(tservice, "");//生成處理數(shù)據(jù)的類(就是生成用于遠(yuǎn)程調(diào)用) 10 11 generate_service_multiface(tservice);//生成一個(gè)實(shí)現(xiàn)多接口的單一的服務(wù)器類 12 13 generate_service_skeleton(tservice);//生成一個(gè)服務(wù)器的框架文件
如果gen_cob_style_為true,還會(huì)生成一些擴(kuò)展功能的類,代碼如下:
1 if (gen_cob_style_) { 2 3 generate_service_interface(tservice, "CobCl"); 4 5 generate_service_interface(tservice, "CobSv"); 6 7 generate_service_null(tservice, "CobSv"); 8 9 generate_service_client(tservice, "Cob"); 10 11 generate_service_processor(tservice, "Cob"); 12 13 generate_service_async_skeleton(tservice); 14 15 }
到此C++的代碼的生成全部結(jié)束,最后調(diào)用close_generator函數(shù)來(lái)完成收尾工作和清理一些資源,如果關(guān)閉文件。
(7)總結(jié)
對(duì)于生成C++代碼這一塊內(nèi)容把基本的生成過(guò)程詳細(xì)分析了一遍,主要集中在整個(gè)流程中。但是很多功能還沒(méi)有詳細(xì)分析或還沒(méi)有涉及到,因?yàn)檎麄€(gè)代碼有4千多行,要完全詳細(xì)用文字分析下來(lái)工作量很多(代碼肯定都看了一遍),而且也覺(jué)得沒(méi)有必要,因?yàn)楹芏喙δ軐?shí)現(xiàn)都挺簡(jiǎn)單,只要一看代碼便能夠理解。
上面分析過(guò)程沒(méi)有提到的功能主要包括:數(shù)據(jù)的序列化和反序列化、具體生成服務(wù)需要的每一個(gè)類等等。其實(shí)整個(gè)代碼并沒(méi)有什么難點(diǎn),主要是必須要思考周全,還有就是注意生成C++代碼的合理性。下面把這個(gè)C++代碼生成過(guò)程函數(shù)的調(diào)用層次用圖形表示如下:

本來(lái)打算繼續(xù)詳細(xì)分析Java和Python的代碼生成的代碼,但是我閱讀了這部分代碼,發(fā)現(xiàn)和C++基本相同,只是由于各種語(yǔ)言語(yǔ)法不相同而在生成代碼的時(shí)候處理不同,但是處理方法和流程都是一樣的,所以就不詳細(xì)分析了,可以參照C++的生成代碼對(duì)照分析。
浙公網(wǎng)安備 33010602011771號(hào)