維護用C/C++ 開發的遺留系統並添加新特性是一項艱難的任務。這涉及幾方面的問題 :理解現有的類層次結構和全局變量,不同的用戶定義類型,以及函數調用圖分析等等。 本文在 C/C++ 項目的上下文中通過示例討論 doxygen 的幾個特性。但是,doxygen 非常 靈活,也可用於用Python、Java、PHP 和其他語言開發的軟件項目。本文的主要目的是幫 助您從 C/C++ 源代碼提取出信息,但也簡要描述了如何用doxygen 定義的標記生成代碼 文檔。
安裝 doxygen
有兩種獲得 doxygen 的方法。可以下載預編譯的可 執行文件,也可以從 SVN 存儲庫下載源代碼並自己編譯。清單 1 演示的是後一種方法。
清單 1. 安裝和構建 doxygen 源代碼
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。
清單 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 文件格式相似。下面是最重要 的標記:
<OUTPUT_DIRECTORY>:必須在這裡提供一個目錄名,例如 /home/user1/documentation,這個目錄是放置生成的文檔文件的位置。如果提供一個不 存在的目錄名,doxygen 會以這個名稱創建具有適當用戶權限的目錄。
<INPUT>:這個標記創建一個以空格分隔的所有目錄的列表,這個列表包含 需要生成文檔的C/C++ 源代碼文件和頭文件。例如,請考慮以下代碼片段:INPUT = /home/user1/project/kernel /home/user1/project/memory
在這裡, doxygen 會從這兩個目錄讀取 C/C++ 源代碼。如果項目只有一個源代碼根目錄,其中有 多個子目錄,那麼只需指定根目錄並把 <RECURSIVE> 標記設置為 Yes。
<FILE_PATTERNS>:在默認情況下,doxygen 會搜索具有典型 C/C++ 擴展 名的文件,比如 .c、.cc、.cpp、.h 和 .hpp。如果 <FILE_PATTERNS> 標記沒有 相關聯的值,doxygen 就會這樣做。如果源代碼文件采用不同的命名約定,就應該相應地 更新這個標記。例如,如果項目使用.c86 作為 C 文件擴展名,就應該在 <FILE_PATTERNS> 標記中添加這個擴展名。
<RECURSIVE>:如果源代 碼層次結構是嵌套的,而且需要為所有層次上的C/C++ 文件生成文檔,就把這個標記設置 為 Yes。例如,請考慮源代碼根目錄層次結構 /home/user1/project/kernel,其中有 /home/user1/project/kernel/vmm 和 /home/user1/project/kernel/asm 等子目錄。如 果這個標記設置為 Yes,doxygen 就會遞歸地搜索整個層次結構並提取信息。
<EXTRACT_ALL>:這個標記告訴 doxygen,即使各個類或函數沒有文檔,也 要提取信息。必須把這個標記設置為 Yes。
<EXTRACT_PRIVATE>:把這個標 記設置為 Yes。否則,文檔不包含類的私有數據成員。
<EXTRACT_STATIC> :把這個標記設置為 Yes。否則,文檔不包含文件的靜態成員(函數和變量)。
清單 3 給出一個 Doxyfile 示例。
清單 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 運行日志示例。
清單 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生 成以下格式的文檔:
UNIX 手冊頁:把 <GENERATE_MAN> 標記設置為 Yes。 在默認情況下,會在 <OUTPUT_DIRECTORY> 指定的目錄中創建 man 子文件夾,生 成的文檔放在這個文件夾中。必須把這個文件夾添加到 MANPATH 環境變量中。
Rich Text Format(RTF):把 <GENERATE_RTF> 標記設置為 Yes。把 <RTF_OUTPUT> 標記設置為希望放置 .rtf 文件的目錄;在默認情況下,文檔放在 OUTPUT_DIRECTORY 中的rtf 子文件夾中。要想支持跨文檔浏覽,應該把 <RTF_HYPERLINKS> 標記設置為 Yes。如果設置這個標記,生成的.rtf 文件會包含 跨文檔鏈接。
Latex:在默認情況下,doxygen生成 Latex 和 HTML 格式的文檔。 在默認的Doxyfile 中,<GENERATE_LATEX> 標記設置為 Yes。另外, <LATEX_OUTPUT> 標記設置為 Latex,這意味著會在 OUTPUT_DIRECTORY 中創建 latex 子文件夾並在其中生成 Latex 文件。
Microsoft® Compiled HTML Help(CHM)格式:把 <GENERATE_HTMLHELP> 標記設置為 Yes。因為在 UNIX 平台 上不支持這種格式,doxygen 只在保存 HTML 文件的文件夾中生成一個 index.hhp 文件 。您必須通過 HTML 幫助編譯器把這個文件轉換為 .chm 文件。
Extensible Markup Language(XML)格式:把 <GENERATE_XML> 標記設置為 Yes。(注意, doxygen 開發團隊還在開發 XML 輸出)。
清單 5 提供的Doxyfile 示例讓 doxygen生成所有格式的文檔。
清單 5.生成多種格式的文檔的 Doxyfile
#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 中的代碼。
清單 6. 使用宏的C++ 代碼示例
#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。
清單 7. 包含多個宏的C++ 源代碼
#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 給出配置文件。
清單 8. 允許有選擇地展開宏的 Doxyfile
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。
清單 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 這樣。
清單 10. 使用EXCLUDE 標記的 Doxyfile
INPUT = /home/user1/src_root
EXCLUDE = /home/user1/src_root/examples /home/user1/src_root/test/memoryleaks
…
生成圖形和圖表
在默認情況下,Doxyfile 把 <CLASS_DIAGRAMS> 標記設置為 Yes。這個標記用來生成類層次結構圖。要想生成 更好的視圖,可以從 Graphviz 下載站點 下載 dot 工具。Doxyfile 中的以下標記用來 生成圖表:
<CLASS_DIAGRAMS>:在 Doxyfile 中這個標記默認設置為 Yes 。如果這個標記設置為 No,就不生成繼承層次結構圖。
<HAVE_DOT>:如果 這個標記設置為 Yes,doxygen 就使用dot 工具生成更強大的圖形,比如幫助理解類成員 及其數據結構的協作圖。注意,如果這個標記設置為 Yes,<CLASS_DIAGRAMS> 標 記就無效了。
<CLASS_GRAPH>:如果 <HAVE_DOT> 標記和這個標記同 時設置為 Yes,就使用dot生成繼承層次結構圖,而且其外觀比只使用 <CLASS_DIAGRAMS> 時更豐富。
<COLLABORATION_GRAPH>:如果 <HAVE_DOT> 標記和這個標記同時設置為 Yes,doxygen 會生成協作圖(還有繼承 圖),顯示各個類成員(即包含)及其繼承層次結構。
清單 11 提供一個使用一 些數據結構的示例。注意,在配置文件中 <HAVE_DOT>、<CLASS_GRAPH> 和 <COLLABORATION_GRAPH> 標記都設置為 Yes。
清單 11. C++ 類和結構示例
struct D {
int d;
};
class A {
int a;
};
class B : public A {
int b;
};
class C : public B {
int c;
D d;
};
圖 1 給出 doxygen 的輸出。
圖 1. 使用dot 工具生成的類繼承圖和協作圖
代碼 文檔樣式
到目前為止,我們都是使用doxygen 從原本沒有文檔的代碼中提取信息 。但是,doxygen 也鼓勵使用文檔樣式和語法,這有助於生成更詳細的文檔。本節討論 doxygen 鼓勵在 C/C++ 代碼中使用的一些常用標記。更多信息參見 參考資料。
每個代碼元素有兩種描述:簡短的和詳細的。簡短描述通常是單行的。函數和類方法還有 第三種描述體內描述(in-body description),這種描述把在函數體中找到的所有注釋 塊集中在一起。比較常用的一些 doxygen 標記和注釋樣式如下:
簡短描述:使用 單行的C++ 注釋,或使用<\brief> 標記。
詳細描述:使用JavaDoc 式的注 釋 /** … test … */(注意開頭的兩個星號 [*])或 Qt 式的注釋 /*! … text … */。
體內描述:類、結構、聯合體和名稱空間等 C++ 元素都有自己的標記,比如 <\class>、<\struct>、<\union> 和 <\namespace>。
為了為全局函數、變量和枚舉類型生成文檔,必須先對對應的文件使用 <\file> 標記。清單 12 給出的示例包含用於四種元素的標記:函數標記(<\fn>)、函數參數標記(<\param>)、變量名標記(<\var>)、用於 #define 的標記(<\def>)以及用來表示與一個代碼片段相關的問題的標記(<\warning>)。
清單 12. 典型的doxygen 標記及其使用方法
/*! \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 就可以幫助任 何開發人員維護和管理遺留系統。