维护用C/C++开发的遗留系统并添加新特性是一项艰难的任务。这涉及几方面的问题:理解现有的类层次结构和全局变量,不同的用户定义类型,以及函数调用图分析等等。本文在C/C++项目的上下文中通过示例讨论 doxygen 的几个特性。但是,doxygen 非常灵活,也可用于用 Python、Java、PHP 和其他语言开发的软件项目。本文的主要目的是帮助您从C/C++源代码提取出信息,但也简要描述了如何用 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 生成源代码的文档需要执行三个步骤。
在 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
在 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++代码进行预处理。但是,在默认情况下,它只进行部分预处理 —— 计算条件编译语句(#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 就可以帮助任何开发人员维护和管理遗留系统。