[转]C 程序文档生成器介绍(doxygen)

程序文档,曾经是程序员的一个头痛问题。写一个程序文档,比较花时间,但不是很难;麻烦的是当程序修改后,程序文档也要跟着同步更新,否则文档和程序就要脱节,文档也就变成没用的东西了。

好在有许多好用的文档生成器来解决这个问题。目前比较流行的C 文档生成器是doxygen。
本文就简单的介绍一下doxygen的文档注释方法,以供初学者参考:

1. 模块定义(单独显示一页)

/*
 * @defgroup 模块名 模块的说明文字
 * @{
 */
 
 ... 定义的内容 ...
 
/** @} */ // 模块结尾
 
2. 分组定义(在一页内分组显示)
/*
 * @name 分组说明文字
 * @{
 */
 
 ... 定义的内容 ...
 
/** @} */
 
3. 变量、宏定义、类型定义简要说明
/** 简要说明文字 */
#define FLOAT float
 
/** @brief 简要说明文字(在前面加 @brief 是标准格式) */
#define MIN_UINT 0
 
/*
 * 分行的简要说明 \n
 *  这是第二行的简要说明
 */
int b;
 
4. 函数说明
/*
 * 简要的函数说明文字 
 *  @param [in] param1 参数1说明
 *  @param [out] param2 参数2说明
 *  @return 返回值说明
 */
int func(int param1, int param2);
 
/*
 * 打开文件 \n
 *  文件打开成功后,必须使用 ::CloseFile 函数关闭。
 *  @param[in] file_name 文件名字符串
 *  @param[in] file_mode 文件打开模式字符串,可以由以下几个模块组合而成:
 *  - r 读取
 *  - w 可写
 *  - a 添加
 *  - t 文本模式(不能与 b 联用)
 *  - b 二进制模式(不能与 t 联用)
 *  @return 返回文件编号
 *  - -1 表示打开文件失败
 
 *  @note 文件打开成功后,必须使用 ::CloseFile 函数关闭
 *  @par 示例:
 *  @code
    // 用文本只读方式打开文件
    int f = OpenFile("d:\test.txt", "rt");
 *  @endcode
 
 *  @see ::ReadFile ::WriteFile ::CloseFile
 *  @deprecated 由于特殊的原因,这个函数可能会在将来的版本中取消。
 */
int OpenFile(const char* file_name, const char* file_mode);
 
5. 枚举类型定义
/** 枚举常量 */
typedef enum TDayOfWeek
{
SUN = 0, /**<  星期天(注意,要以 “<” 小于号开头) */
MON = 1, /**<  星期一 */
TUE = 2, /**<  星期二 */
WED = 3, /**<  星期三 */
THU = 4, /**<  星期四 */
FRI = 5, /**<  星期五 */
SAT = 6  /**<  星期六 */
}
/** 定义类型 TEnumDayOfWeek */
TEnumDayOfWeek;  
  
6. 项目符号标记
  /* 
   *  A list of events:
   *    - mouse events
   *         -# mouse move event
   *         -# mouse click event\n
   *            More info about the click event.
   *         -# mouse double click event
   *    - keyboard events
   *         -# key down event
   *         -# key up event
   *
   *  More text here.
   */
 

结果为:

A list of events:

  • mouse events
    1. mouse move event
    2. mouse click event
      More info about the click event.
    3. mouse double click event
  • keyboard events
    1. key down event
    2. key up event

More text here.

代码示范:

/**//*
 * @defgroup EXAMPLES 自动注释文档范例
 * @author  沐枫
 * @version 1.0
 * @date    2004-2005
 * @{
 */

/**//*
 * @name 文件名常量
 * @{
 */
/**//** 日志文件名 */
#define LOG_FILENAME "d:\log\debug.log"
/**//** 数据文件名 */
#define DATA_FILENAME "d:\data\detail.dat"
/**//** 存档文件名 */
#define BAK_FILENAME "d:\data\backup.dat"
/**//** @}*/ // 文件名常量
/**//*
 * @name 系统状态常量
 *  @{
 */
 
/**//** 正常状态 */
#define SYS_NORMAL 0
/**//** 故障状态 */
#define SYS_FAULT 1
/**//** 警告状态 */
#define SYS_WARNNING 2
/**//** @}*/ // 系统状态常量
/**//** 枚举常量 */
typedef enum TDayOfWeek
{
        SUN = 0, /**//**< 星期天 */
        MON = 1, /**//**< 星期一 */
        TUE = 2, /**//**< 星期二 */
        WED = 3, /**//**< 星期三 */
        THU = 4, /**//**< 星期四 */
        FRI = 5, /**//**< 星期五 */
        SAT = 6  /**//**< 星期六 */
}
/**//** 定义类型 TEnumDayOfWeek */
TEnumDayOfWeek; 
/**//** 定义类型 PEnumDayOfWeek */
typedef TEnumDayOfWeek* PEnumDayOfWeek;
/**//** 定义枚举变量 enum1 */
TEnumDayOfWeek enum1;       
/**//** 定义枚举指针变量 enum2 */
PEnumDayOfWeek p_enum2;
/**//*
 * @defgroup FileUtils 文件操作函数
 * @{
 */
/**//*
 * 打开文件 \n
 *  文件打开成功后,必须使用 ::CloseFile 函数关闭。
 *  @param[in] file_name 文件名字符串
 *  @param[in] file_mode 文件打开模式字符串,可以由以下几个模块组合而成:
 *  - r 读取
 *  - w 可写
 *  - a 添加
 *  - t 文本模式(不能与 b 联用)
 *  - b 二进制模式(不能与 t 联用)
 *  @return 返回文件编号
 *  - -1 表示打开文件失败
 
 *  @note 文件打开成功后,必须使用 ::CloseFile 函数关闭
 *  @par 示例:
 *  @code
    // 用文本只读方式打开文件
    int f = OpenFile("d:\test.txt", "rt");
 *  @endcode
 
 *  @see ::ReadFile ::WriteFile ::CloseFile
 *  @deprecated 由于特殊的原因,这个函数可能会在将来的版本中取消。
 */
int OpenFile(const char* file_name, const char* file_mode);
/**//*
 * 读取文件
 *  @param[in] file 文件编号,参见:::OpenFile
 *  @param[out] buffer 用于存放读取的文件内容
 *  @param[in] len 需要读取的文件长度
 *  @return 返回读取文件的长度
 *  - -1 表示读取文件失败
 
 *  @pre \e file 变量必须使用 ::OpenFile 返回值
 *  @pre \e buffer 不能为 NULL
 *  @see ::OpenFile ::WriteFile ::CloseFile
 */
int ReadFile(int file, char* buffer, int len);
/**//*
 * 写入文件
 *  @param[in] file 文件编号,参见:::OpenFile
 *  @param[in] buffer 用于存放将要写入的文件内容
 *  @param[in] len 需要写入的文件长度
 *  @return 返回写入的长度
 *  - -1 表示写入文件失败
 
 *  @pre \e file 变量必须使用 ::OpenFile 返回值
 *  @see ::OpenFile ::ReadFile ::CloseFile
 */
int WriteFile(int file, const char* buffer, int len);
/**//*
 * 关闭文件
 *  @param file 文件编号,参见:::OpenFile
 *  @retval 0  为成功
 *  @retval -1 表示失败
 
 *  @see ::OpenFile ::WriteFile ::ReadFile
 *  @deprecated 由于特殊的原因,这个函数可能会在将来的版本中取消。
 */
int CloseFile(int file);
/**//** @}*/ // 文件操作函数
/**//** @}*/ // 自动注释文档范例

生成的chm文档截图:



...

阅读全文

c Comments(0) 2008年3月28日 04:03

学习autoconf和automake

autoconf和automake是啥?这要从类Unix系统的程序编译说起。一般一个真正的工程肯定不只一个文件,而多个文件的编译是件很麻烦的事情(最简单的就是用gcc或者g 后面接着多个文件),再加上要推出跨平台的(一般只是跨不同的类Unix平台),还有啥包依赖啊什么的,很麻烦嗯。于是就有了一个工具叫make,它接收一个名为Makefile的文件作为参数,自动地进行编译,还可以在Makefile里设置接受不同的选项,然后人们就可以make install、make all什么的了。
然而不同系统的编译要用不同的编译参数,但是开源软件不可能带多个Makefile,而且又难写,囧么办?于是有了configure脚本,它自动检测系统,并接受一个Makefile.in文件,根据它来生成Makefile。
然而confiure脚本和Makefile.in还是很难写,至少我不会写,而且看得也眼花。于是GNU推出了autoconf和automake,用于生成configure脚本和Makefile.in文件。其中autoconf是用来生成configure的,automake是用来生成Makefile.in的。

貌似说了很多废话。东西是拿来用的,不是拿来吹的,所以就拿来用吧。

首先看看autoconf怎么用。autoconf接受一个configure.ac的文件,根据里面的内容来生成configure脚本。怎么还有文件?嗯,总是要有输入的嘛,反正简单得多就是了(大型软件的configure.ac可能也挺复杂,不过总比自己写configure要来得轻松得多)。
现在一步一步来看看最简单的configure.ac里有些什么,首先是两句初始化命令:

AC_INIT(src/main.cpp)
AM_INIT_AUTOMAKE(main, 0.1)
这两句是所有configure.ac里都必须有的,AC_INIT的参数是你main函数所在的文件(包括路径),AM_INIT_AUTOMAKE指定了程序的名称和版本号。名称可以随意起,只要符合标识符规则就行。注意第二行的开头是AM而不是AC。
接下来定义程序语言,C语言用AC_PROG_CC,C 用AC_PROG_CXX,其他语言请自行搜索。
然后可以加一些其他选项了,我见过的是AC_PROG_INSTALL、PKG_CHECK_MODULES和AC_SUBST,目前对它们还没什么了解,先放一边。
如果要用到库(也就是有个目录的代码本身不是文件,是给其他目录include用的),就还要加上这么一句:
AC_PROG_RANLIB
最后指定要输出的Makefile文件,注意是每个有代码(或者有Makefile.am)的地方都要输出,不同的文件之间用空格隔开。大概类似这个样子:
AC_OUTPUT(Makefile src/Makefile)
这就是一个最简单的configure.ac文件了

再看看Makefile.am。
在你的代码所存放的地方要定义bin_PROGRAMS,如
bin_PROGRAMS = main
这个定义的就是编译好的可执行程序名。
然后就要定义程序文件,类似这个格式:
main_SOURCES = main.cpp a.h a.cpp
_SOURCES 前面的要与bin_PROGRAMS所定义的相一致,如果写的是bin_PROGRAMS = asdf,那么相应地要改为asdf_SOURCES。之后要包含该Makefile.am文件所在目录中所有的程序文件(当然,没用到的可以不包含),用空格隔开。不在这个目录下的(哪怕是在这个目录的子目录下)都不要包含进去。
那么子目录下的文件怎么办?我们可以用SUBDIRS指定子目录,如:
SUBDIRS = sub abc
相邻的子目录用空格分开。SUBDIRS里指定的每个子目录中都必须要有Makefile.am(相应地在configure.ac里也要加入到AC_OUTPUT中)。
要注意一点是bin_PROGRAMS定义的是一个可执行文件,也就是说之后的XXX_PROGRAMS中定义的文件必须有一个是有main函数的。
要是我们的子目录中的代码不是一个独立的程序,只是拿来给其他目录的程序include的怎么办?要用noinst_LIBRARIES来将它定义为库,如
noinst_LIBRARIES = lib.a
之后和定义了bin_PROGRAMS一样定义XXX_SOURCES,例如根据上面noinst_LIBRARIES所定义的,我们可以这样来定义:
lib_a_SOURCES = a.cpp a.h
注意“.”要改成“_”,也誻lib.a在作为XXX_SOURCES的头部时变成了lib_a
最后哪里引用了这些代码,要加入相应地语句。比方说,这个目录名字为sub,在它的上层目录要引用到这些代码,那么要在它的上层目录中的Makefile.am中加入这么一句:
LDADD = sub/lib.a
来指定调用了这个目录的代码(其实我们是把sub目录下的代码编译成了一个库,库文件为lib.a,LDADD就是指定要调用哪个库)。
最后,别忘了使用库时,要给configure.ac中加入AC_PROG_RANLIB

由于Makefile.am有多个,刚才的介绍可能有点乱,下面来总结一下Makefile.am是怎么放的:
  1. 根目录(configure.ac所在的目录)必须有一个Makefile.am
  2. 所有有需要编译的代码文件的目录下必须有一个Makefile.am
  3. 如果一个目录中有Makefile.am,那么必须在它的父目录中的Makefile.am里用SUBDIRS指定它
于是这两个文件大致介绍完了,当然,只是介绍了要编译一个简单程序的最基本、最通用的部分,没介绍的东西(也就是我现在还不懂的东西)多着呢。
在介绍如何使用这两个文件之前,首先要说说另一个工具:autoheader。它用来生成一个config.h文件,里面包含一些和平台相关的程序代码,供程序使用。也就是说,你可以在程序里include这个header.h,然后使用其中的某些定义好的常量什么的。由于我没用过,所以对它的介绍也就到此为止了。

现在到了使用这些文件的时候了。首先我们要运行的是autoheader(当然,如果不想要config.h的话,就跳过这一步吧)。
之后要运行的是aclocal,它的作用是生成autoconf所需要的一些宏的定义(也就是生成我们在configure.ac中所使用的扩展宏)。
然后运行autoconf。
之后理论上应该是automake,不过automake它还要求你必须有一些额外的文件,它们是:install-sh、missing、depcomp、INSTALL、NEWS、README、AUTHORS、ChangeLog、COPYING,它们是一个符合GNU规范的代码文件结构所必须的文件。automake可以通过附加--add-missing参数自动生成其中的一些文件(install-sh、missing、depcomp、INSTALL、COPYING),剩下的要自己创建。我们可以用touch命令来创建一个空文件,然后再运行automake。
之后就可以运行configure脚本来创建Makefile,再运行make来编译了。
总结一下,调用的命令应该是:
autoheader
aclocal
autoconf
automake
./configure
make
如果没有automake所需要的那些其他文件的话,应该是这样:
autoheader
aclocal
autoconf
touch NEWS README AUTHORS ChangeLog
automake --add-missing
./configure
make
可以没有autoheader(如果不需要它生成的config.h)的话
编译生成的可执行文件不一定是在程序的根目录的。若一个Makefile.am定义了bin_PROGRAMS,那么它所定义的bin_PROGRAMS相对应的可执行文件会与它在同一个目录下。

下面来看看一个简单的例子。程序总共有3个文件,分别是main.cpp、a.h和a.cpp,其中main.cpp在src目录下,a.h和a.cpp是main.cpp所需要引用的代码,位于src目录的sub子目录下。我们想为main.cpp生成名为hello的可执行文件。

首先在程序根目录下编写configure.ac文件:
AC_INIT(src/main.cpp)
AM_INIT_AUTOMAKE(hello, 0.1)
AC_PROG_CXX
AC_PROG_RANLIB
AC_OUTPUT(Makefile src/Makefile src/sub/Makefile)

然后是程序根目录下的Makefile.am文件:
SUBDIRS = src

之后是src目录下的Makefile.am:
bin_PROGRAMS = main
main_SOURCES = main.cpp
SUBDIRS = sub
LDADD = sub/lib.a
最后是src/sub目录下的Makefile.am:
noinst_LIBRARIES = lib.a
lib_a_SOURCES = a.cpp a.h
编译成功后,就可以在src目录下看到hello文件了

一些参考资料:
Using Automake and Autoconf with C
The Goat Book 中文版第五章
autoconf文档
automake文档...

阅读全文

c Comments(0) 2008年3月19日 05:03