Thrift之代碼生成器Compiler原理及源碼詳細解析3


我的新浪微博:http://weibo.com/freshairbrucewoo

歡迎大家相互交流,共同提高技術。

 

生成C++語言代碼的代碼詳解

 

這個功能是由t_cpp_generator類實現(在文件t_cpp_generator.cc定義和實現),直接繼承至t_oop_generator類(這個類是所有面向對象語言生成器類的直接基類,封裝了面向對象語言生成器共有的特征與行為),而t_oop_generator又從t_generator繼承(上面已經介紹),下面詳細分析這個類是怎樣生成C++語言的代碼文件的。這個還有從上面介紹的generate_program函數開始說起,因為這個函數才是控制整個代碼生成的總樞紐。

首先執行的是構造函數,這個構造函數做了一些最基本的初始化,一個是傳遞擁有生成代碼的符號資源的t_program對象到父類,第二個功能就是根據可選項參數初始化一些bool變量,以便后面根據這些bool變量做相應的處理,代碼很簡單就不列出來了,下面用一個表格說明各個bool變量的作用(或功能)。

 

gen_pure_enums_

是否生成純凈的枚舉類型,而不是采用類包裝的形式

gen_dense_

是否應該TDenseProtocol生成本地反射的元數據。

gen_templates_

是否要生成模板化的讀/寫方法

use_include_prefix_

是否應該為了thrift生成的其他頭文件在#include中使用前綴路徑

gen_cob_style_

是否應該生成繼承擴展功能類(主要是異步)

gen_no_client_completion_

是否應該省略客戶端類調用completion__()

 

構造函數只是做了最基本的初始化,更詳細的初始化是上面介紹的代碼生成器初始化函數init_generator,那我們看看C++代碼生成器是怎么詳細初始化的,都做了一些什么樣的工作和實現了一些什么的功能。我們分步驟介紹這一個函數:

第一步:制作代碼文件的輸出目錄:MKDIR(get_out_dir().c_str());MKDIR是一個宏函數,調用了mkdir來創建目錄;

第二部:創建代碼頭文件和實現文件,如果需要生成模板化的讀和寫的方法還會創建一個文件單獨實現,代碼如下:

 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   }

 

這里需要說明幾個用於輸出流的成員變量,之所以定義成員變量是因為很多函數會用到,這樣就不用用參數來傳遞它們了,它們定義和說明如下:

 1 std::ofstream f_types_;//專門用於類型聲明的輸出流,也就是頭文件(.h文件)
 2 
 3 std::ofstream f_types_impl_;//專門用於類型實現的輸出流,也就是實現文件(.cpp文件)
 4 
 5   std::ofstream f_types_tcc_;//專門用於模板化的讀和寫方法實現的輸出流
 6 
 7   std::ofstream f_header_;//專門用於服務聲明生成的輸出流
 8 
 9   std::ofstream f_service_;//專門用於服務實現生成的輸出流
10 
11   std::ofstream f_service_tcc_;//專門用於模板的服務的輸出流

 

第三步:為每個文件打印頭部注釋,注釋的作用就是說明這個文件是由Thrift自動生成的,代碼如下:

1 f_types_ << autogen_comment();
2 
3 f_types_impl_ << autogen_comment();
4 
5 f_types_tcc_ << autogen_comment();

 

第四步:開始ifndef

第五步:包含各種頭文件

第六步:打開命名空間,生成的代碼都是在一個命令空間里面的。

以上步驟的功能都比較簡單,主要就是注意輸出格式和邏輯處理。通過這些功能基本內容都做好了,下面就是真正開始生成具體類型和服務的時候了,每一種數據類型都由一個單獨的函數來負責生成為代碼。

1)枚舉類型生成函數generate_enum

首先在頭文件中生成定義枚舉類型的代碼,具體的過程就是得到枚舉的所有常量值和枚舉類型的名稱,然后根據C++定義枚舉類型的語法輸出代碼到頭文件,輸出過程中根據是否需要用類來包裝而所有不同,同時生成的代碼也需要格式控制。具體實現如下:

 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;

 

接着在后面在實現文件中定義一個整型數組和一個字符的數組並用定義的枚舉類型的常量值來初始化這兩個數組,后然在說這兩個數組的值初始化一個map,其實這么做的目的就是為了測試這個枚舉類型定義是否正確。

最后調用函數generate_local_reflection決定是否為TDenseProtocol協議生成對應類型的本地反射類型。這個函數功能比較復雜,后面單獨詳細講解。

2)類型定義生成函數generate_typedef

此函數功能簡單,就是在頭文件中生成一個typedef的定義,就只有一句實現:

1 f_types_ <<  indent() << "typedef " << type_name(ttypedef->get_type(), true) << " " 
2 
3 << ttypedef->get_symbolic() << ";" << endl << endl;

 

3)常量類型生成函數generate_consts

常量類型的實現是采用一個類來包裝所有的常量並且使用單獨的文件來實現,所有首先創建常量類型定義頭文件和實現文件,代碼如下:

 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());

 

接着按照就開始按照類的定義格式在頭文件中生成定義類的代碼並在實現文件中定義這個類的常量類型;在這個類的構造函數中給定義的數據類型賦值:

 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;

 

其中調用了print_const_value函數來根據數據類型來賦值,這些定義在類中的成員變量本身不是常量類型,只是在實現文件中定義了一個類的全局常量對象,在頭文件中聲明,以便其他地方可以被使用。

4)異常類型生成函數generate_xception

這個函數其實調用下面需要詳細分析的一個函數實現的,就是generate_struct函數,因為異常也是通過結構體來定義和實現的。不過C++語言生成器中也自己實現了這個函數,不過它是調用generate_cpp_struct函數實現,C++generate_struct函數也是調用這個函數實現,只是傳遞一個bool變量來區分是否是異常類型,具體的實現在分析generate_struct函數時一起詳細分析了,因為它們的基本實現功能都是相同的。

5)結構體類型生成函數generate_struct

上面已經說了這個函數也是調用generate_cpp_struct函數實現,也就是說異常類型和結構體類型都是用同樣的流程實現的,它們都是定義為一個類,只是異常都從TException繼承,而一般的結構體沒有。首先調用函數generate_struct_definition在頭文件中生成定義類的代碼,這個過程如下:

第一步:得到所有的成員變量;

第二步:根據是否有可選成員決定是否定義一個結構體_XXX_isset,這個結果主要針對需要定義的類的可選成員而定義一些bool變量,來標識這些可選成員變量是否存在。設計這個功能的目的是為了靈活控制數據傳輸的結構;

第三步:開始生成定義類(IDL文件中定義的structC++都是用class來實現)的代碼,生成的代碼主要包括默認的構造函數、析構函數、各個字段、比較函數(等於、不等於和小於)等;

第四步:最后一步生成一個模板的讀和寫數據的函數的聲明,模板參數是協議類型,實現代碼如下:

 1 if (read) {//讀數據的模板函數
 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) {//寫數據的模板函數
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 }

 

然后調用函數generate_struct_fingerprint在實現文件中初始化兩個靜態變量,一個是字符串,一個是8位的整型數組,這兩個變量都是用來唯一的標識一個類。這個歌標識符的作用就是用於生成本地的反射類型,當使用TDenseProtocol協議傳輸數據時會用到。

接着兩次調用generate_local_reflection函數分別來聲明和定義用於類的本地反射的類型,調用generate_local_reflection_pointer函數來生成一個類的靜態指針的本地反射類型。

最后分別調用函數generate_struct_reader和generate_struct_writer實現數據讀和寫函數。到此整個IDL定義的struct類型生成為C++的代碼就完成了。

6)服務類型生成函數generate_service

這個函數的功能是最復雜的,它會做很多的工作(分別調用其它函數來實現),也會生成單獨的頭文件和實現文件。生成頭文件的代碼如下:

1 string f_header_name = get_out_dir()+svcname+".h";
2 
3   f_header_.open(f_header_name.c_str());

 

下面就開始在頭文件中生成一些包含頭文件的代碼。

生成實現文件的代碼:

1 string f_service_name = get_out_dir()+svcname+".cpp";
2 
3   f_service_.open(f_service_name.c_str());

 

后面也是生成一些包含頭文件的代碼。接着就開始生成正在的各種實現這個服務的代碼了,如下:

 1 generate_service_interface(tservice, "");//生成服務的接口類(在C++為抽象類)
 2 
 3   generate_service_null(tservice, "");//生成一個空實現服務接口類的類
 4 
 5   generate_service_helpers(tservice);//生成一些幫助類,如參數類、返回結果類等
 6 
 7   generate_service_client(tservice, "");//生成一個客戶類
 8 
 9   generate_service_processor(tservice, "");//生成處理數據的類(就是生成用於遠程調用)
10 
11   generate_service_multiface(tservice);//生成一個實現多接口的單一的服務器類
12 
13   generate_service_skeleton(tservice);//生成一個服務器的框架文件

 

如果gen_cob_style_為true,還會生成一些擴展功能的類,代碼如下:

 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++的代碼的生成全部結束,最后調用close_generator函數來完成收尾工作和清理一些資源,如果關閉文件。

7)總結

對於生成C++代碼這一塊內容把基本的生成過程詳細分析了一遍,主要集中在整個流程中。但是很多功能還沒有詳細分析或還沒有涉及到,因為整個代碼有4千多行,要完全詳細用文字分析下來工作量很多(代碼肯定都看了一遍),而且也覺得沒有必要,因為很多功能實現都挺簡單,只要一看代碼便能夠理解。

上面分析過程沒有提到的功能主要包括:數據的序列化和反序列化、具體生成服務需要的每一個類等等。其實整個代碼並沒有什么難點,主要是必須要思考周全,還有就是注意生成C++代碼的合理性。下面把這個C++代碼生成過程函數的調用層次用圖形表示如下:

 

本來打算繼續詳細分析JavaPython的代碼生成的代碼,但是我閱讀了這部分代碼,發現和C++基本相同,只是由於各種語言語法不相同而在生成代碼的時候處理不同,但是處理方法和流程都是一樣的,所以就不詳細分析了,可以參照C++的生成代碼對照分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM