維護用 C/C++ 開發的遺留系統並添加新特性是一項艱難的任務。幸運的是,doxygen 可以幫助您完成這個任務。doxygen 是一種用於 C/C++、Java™、Python 和其他編程語言的文檔系統。本文在 C/C++ 項目的上下文中討論 doxygen 的特性,以及如何用 doxygen 定義的標記生成代碼文檔。
維護用 C/C++ 開發的遺留系統並添加新特性是一項艱難的任務。這涉及幾方面的問題:理解現有的類層次結構和全局變數,不同的用戶定義類型,以及函數調用圖分析等等。本文在 C/C++ 項目的上下文中通過示例討論 doxygen 的幾個特性。但是,doxygen 非常靈活,也可用於用 Python、Java、PHP 和其他語言開發的軟體項目。本文的主要目的是幫助您從 C/C++ 源代碼提取出信息,但也簡要描述了如何用 doxygen 定義的標記生成代碼文檔。
安裝 doxygen
有兩種獲得 doxygen 的方法。可以下載預編譯的可執行文件,也可以從 SVN 存儲庫下載源代碼並自己編譯。清單 1 演示的是后一種方法。
bash-2.05$ svn co https://doxygen.svn.sourceforge.net/svnroot/doxygen/trunk doxygen-svn bash-2.05$ cd doxygen-svn bash-2.05$ ./configure –prefix=/home/user1/bin bash-2.05$ make bash-2.05$ make install |
注意,配置腳本把編譯的源代碼存儲在 /home/user1/bin 中(進行編譯后,會在 PATH 變數中添加這個目錄),因為並非每個 UNIX® 用戶都有寫 /usr 文件夾的許可權。另外,需要用 svn 實用程序下載源代碼。
使用 doxygen 生成文檔
使用 doxygen 生成源代碼的文檔需要執行三個步驟。
生成配置文件
在 shell 提示上,輸入命令 doxygen -g 。這個命令在當前目錄中生成一個可編輯的配置文件 Doxyfile。可以改變這個文件名,在這種情況下,應該調用 doxygen -g <user-specified file name>,見 清單 2。
bash-2.05b$ doxygen -g Configuration file 'Doxyfile' created. Now edit the configuration file and enter doxygen Doxyfile to generate the documentation for your project bash-2.05b$ ls Doxyfile Doxyfile |
編輯配置文件
配置文件採用 <TAGNAME> = <VALUE> 這樣的結構,與 Make 文件格式相似。下面是最重要的標記:
INPUT = /home/user1/project/kernel /home/user1/project/memory |
在這裡,doxygen 會從這兩個目錄讀取 C/C++ 源代碼。如果項目只有一個源代碼根目錄,其中有多個子目錄,那麼只需指定根目錄並把 <RECURSIVE> 標記設置為 Yes。
清單 3 給出一個 Doxyfile 示例。
OUTPUT_DIRECTORY = /home/user1/docs EXTRACT_ALL = yes EXTRACT_PRIVATE = yes EXTRACT_STATIC = yes INPUT = /home/user1/project/kernel #Do not add anything here unless you need to. Doxygen already covers all #common formats like .c/.cc/.cxx/.c++/.cpp/.inl/.h/.hpp FILE_PATTERNS = RECURSIVE = yes |
運行 doxygen
在 shell 提示下輸入 doxygen Doxyfile(或者已為配置文件選擇的其他文件名)運行 doxygen。在最終生成 Hypertext Markup Language(HTML)和 Latex 格式(默認)的文檔之前,doxygen 會顯示幾個消息。在生成文檔期間,在 <OUTPUT_DIRECTORY> 標記指定的文件夾中,會創建兩個子文件夾 html 和 latex。清單 4 是一個 doxygen 運行日誌示例。
Searching for include files... Searching for example files... Searching for images... Searching for dot files... Searching for files to exclude Reading input files... Reading and parsing tag files Preprocessing /home/user1/project/kernel/kernel.h … Read 12489207 bytes Parsing input... Parsing file /project/user1/project/kernel/epico.cxx … Freeing input... Building group list... .. Generating docs for compound MemoryManager::ProcessSpec … Generating docs for namespace std Generating group index... Generating example index... Generating file member index... Generating namespace member index... Generating page index... Generating graph info page... Generating search index... Generating style sheet... |
文檔輸出格式
除了 HTML 之外,doxygen 還可以生成幾種輸出格式的文檔。可以讓 doxygen 生成以下格式的文檔:
清單 5 提供的 Doxyfile 示例讓 doxygen 生成所有格式的文檔。
#for HTML GENERATE_HTML = YES HTML_FILE_EXTENSION = .htm #for CHM files GENERATE_HTMLHELP = YES #for Latex output GENERATE_LATEX = YES LATEX_OUTPUT = latex #for RTF GENERATE_RTF = YES RTF_OUTPUT = rtf RTF_HYPERLINKS = YES #for MAN pages GENERATE_MAN = YES MAN_OUTPUT = man #for XML GENERATE_XML = YES |
doxygen 中的特殊標記
doxygen 包含幾個特殊標記。
C/C++ 代碼的預處理
為了提取信息,doxygen 必須對 C/C++ 代碼進行預處理。但是,在默認情況下,它只進行部分預處理 —— 計算條件編譯語句(#if…#endif),但是不執行宏展開。請考慮 清單 6 中的代碼。
#include <cstring> #include <rope> #define USE_ROPE #ifdef USE_ROPE #define STRING std::rope #else #define STRING std::string #endif static STRING name; |
通過源代碼中定義的 <USE_ROPE>,doxygen 生成的文檔如下:
Defines #define USE_ROPE #define STRING std::rope Variables static STRING name |
在這裡可以看到 doxygen 執行了條件編譯,但是沒有對 STRING 執行宏展開。Doxyfile 中的 <ENABLE_PREPROCESSING> 標記在默認情況下設置為 Yes。為了執行宏展開,還應該把 <MACRO_EXPANSION> 標記設置為 Yes。這會使 doxygen 產生以下輸出:
Defines #define USE_ROPE #define STRING std::string Variables static std::rope name |
如果把 <ENABLE_PREPROCESSING> 標記設置為 No,前面源代碼的 doxygen 輸出就是:
Variables static STRING name |
注意,文檔現在沒有定義,而且不可能推導出 STRING 的類型。因此,總是應該把 <ENABLE_PREPROCESSING> 標記設置為 Yes。
在文檔中,可能希望只展開特定的宏。為此,除了把 <ENABLE_PREPROCESSING> 和 <MACRO_EXPANSION> 標記設置為 Yes 之外,還必須把 <EXPAND_ONLY_PREDEF> 標記設置為 Yes(這個標記在默認情況下設置為 No),並在 <PREDEFINED> 或 <EXPAND_AS_DEFINED> 標記中提供宏的細節。請考慮 清單 7 中的代碼,這裡只希望展開宏 CONTAINER。
#ifdef USE_ROPE #define STRING std::rope #else #define STRING std::string #endif #if ALLOW_RANDOM_ACCESS == 1 #define CONTAINER std::vector #else #define CONTAINER std::list #endif static STRING name; static CONTAINER gList; |
清單 8 給出配置文件。
ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES EXPAND_AS_DEFINED = CONTAINER … |
下面的 doxygen 輸出只展開了 CONTAINER:
Defines #define STRING std::string #define CONTAINER std::list Variables static STRING name static std::list gList |
注意,只有 CONTAINER 宏被展開了。在 <MACRO_EXPANSION> 和 <EXPAND_ONLY_PREDEF> 都設置為 Yes 的情況下,<EXPAND_AS_DEFINED> 標記只選擇展開等號操作符右邊列出的宏。
對於預處理過程,要注意的最後一個標記是 <PREDEFINED>。就像用 -D 開關向 C++ 編譯器傳遞預處理器定義一樣,使用這個標記定義宏。請考慮 清單 9 中的 Doxyfile。
ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES EXPAND_AS_DEFINED = PREDEFINED = USE_ROPE= \ ALLOW_RANDOM_ACCESS=1 |
下面是 doxygen 生成的輸出:
Defines #define USE_CROPE #define STRING std::rope #define CONTAINER std::vector Variables static std::rope name static std::vector gList |
在使用 <PREDEFINED> 標記時,宏應該定義為 <macro name>=<value> 形式。如果不提供值,比如簡單的 #define,那麼只使用 <macro name>=<spaces> 即可。多個宏定義以空格或反斜杠(\)分隔。
從文檔生成過程中排除特定文件或目錄
在 Doxyfile 中的 <EXCLUDE> 標記中,添加不應該為其生成文檔的文件或目錄(以空格分隔)。因此,如果提供了源代碼層次結構的根,並要跳過某些子目錄,這將非常有用。例如,如果層次結構的根是 src_root,希望在文檔生成過程中跳過 examples/ 和 test/memoryleaks 文件夾,Doxyfile 應該像 清單 10 這樣。
INPUT = /home/user1/src_root EXCLUDE = /home/user1/src_root/examples /home/user1/src_root/test/memoryleaks … |
生成圖形和圖表
在默認情況下,Doxyfile 把 <CLASS_DIAGRAMS> 標記設置為 Yes。這個標記用來生成類層次結構圖。要想生成更好的視圖,可以從 Graphviz 下載站點 下載 dot 工具。Doxyfile 中的以下標記用來生成圖表:
清單 11 提供一個使用一些數據結構的示例。注意,在配置文件中 <HAVE_DOT>、<CLASS_GRAPH> 和 <COLLABORATION_GRAPH> 標記都設置為 Yes。
struct D { int d; }; class A { int a; }; class B : public A { int b; }; class C : public B { int c; D d; }; |
圖 1 給出 doxygen 的輸出。
代碼文檔樣式
到目前為止,我們都是使用 doxygen 從原本沒有文檔的代碼中提取信息。但是,doxygen 也鼓勵使用文檔樣式和語法,這有助於生成更詳細的文檔。本節討論 doxygen 鼓勵在 C/C++ 代碼中使用的一些常用標記。更多信息參見 參考資料。
每個代碼元素有兩種描述:簡短的和詳細的。簡短描述通常是單行的。函數和類方法還有第三種描述體內描述(in-body description),這種描述把在函數體中找到的所有註釋塊集中在一起。比較常用的一些 doxygen 標記和註釋樣式如下:
為了為全局函數、變數和枚舉類型生成文檔,必須先對對應的文件使用 <\file> 標記。清單 12 給出的示例包含用於四種元素的標記:函數標記(<\fn>)、函數參數標記(<\param>)、變數名標記(<\var>)、用於 #define 的標記(<\def>)以及用來表示與一個代碼片段相關的問題的標記(<\warning>)。
/*! \file globaldecls.h \brief Place to look for global variables, enums, functions and macro definitions */ /** \var const int fileSize \brief Default size of the file on disk */ const int fileSize = 1048576; /** \def SHIFT(value, length) \brief Left shift value by length in bits */ #define SHIFT(value, length) ((value) << (length)) /** \fn bool check_for_io_errors(FILE* fp) \brief Checks if a file is corrupted or not \param fp Pointer to an already opened file \warning Not thread safe! */ bool check_for_io_errors(FILE* fp); |
下面是生成的文檔:
Defines #define SHIFT(value, length) ((value) << (length)) Left shift value by length in bits. Functions bool check_for_io_errors (FILE *fp) Checks if a file is corrupted or not. Variables const int fileSize = 1048576; Function Documentation bool check_for_io_errors (FILE* fp) Checks if a file is corrupted or not. Parameters fp: Pointer to an already opened file Warning Not thread safe! |
結束語
本文討論如何用 doxygen 從遺留的 C/C++ 代碼提取出大量相關信息。如果用 doxygen 標記生成代碼文檔,doxygen 會以容易閱讀的格式生成輸出。只要以適當的方式使用,doxygen 就可以幫助任何開發人員維護和管理遺留系統。(責任編輯:A6)
[火星人 ] 學慣用 doxygen 生成源碼文檔已經有1264次圍觀