用作 Doxygen 输入的源文件可以通过 Doxygen 内置的 C 预处理器进行解析。
默认情况下,Doxygen 仅进行部分预处理。也就是说,它会评估条件编译语句(例如 #if
)和宏定义,但不会执行宏展开。
因此,如果你有以下代码片段
#define VERSION 200 #define CONST_STRING const char * #if VERSION >= 200 static CONST_STRING version = "2.xx"; #else static CONST_STRING version = "1.xx"; #endif
那么默认情况下,Doxygen 会将以下内容提供给其解析器
#define VERSION #define CONST_STRING static CONST_STRING version = "2.xx";
你可以通过在配置文件中将 ENABLE_PREPROCESSING 设置为 NO
来禁用所有预处理。在上面的例子中,Doxygen 将读取两个语句,即
static CONST_STRING version = "2.xx"; static CONST_STRING version = "1.xx";
如果你想展开 CONST_STRING
宏,你应该在配置文件中将 MACRO_EXPANSION 标签设置为 YES
。那么预处理后的结果将是
#define VERSION #define CONST_STRING static const char * version = "2.xx";
注意,Doxygen 现在会展开所有宏定义(如果需要则递归展开)。这通常太多了。因此,Doxygen 还允许你仅展开你明确指定的那些定义。为此,你必须将 EXPAND_ONLY_PREDEF 标签设置为 YES
,并在 PREDEFINED 或 EXPAND_AS_DEFINED 标签后指定宏定义。
预处理器提供帮助的一个典型例子是处理 Microsoft 的语言扩展:__declspec
。GNU 的 __attribute__
扩展也是如此。这是一个示例函数。
extern "C" void __declspec(dllexport) ErrorMsg( String aMessage,...);
如果不做任何处理,Doxygen 会感到困惑,并将 __declspec
视为某种函数。为了帮助 Doxygen,通常使用以下预处理器设置
ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES PREDEFINED = __declspec(x)=
这将确保在 Doxygen 解析源代码之前移除 __declspec(dllexport)
。
类似设置可用于从输入中移除 __attribute__
表达式
ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES PREDEFINED = __attribute__(x)=
对于更复杂的例子,假设你有一个名为 IUnknown
的抽象基类的以下模糊代码片段:
/*! A reference to an IID */ #ifdef __cplusplus #define REFIID const IID & #else #define REFIID const IID * #endif /*! The IUnknown interface */ DECLARE_INTERFACE(IUnknown) { STDMETHOD(HRESULT,QueryInterface) (THIS_ REFIID iid, void **ppv) PURE; STDMETHOD(ULONG,AddRef) (THIS) PURE; STDMETHOD(ULONG,Release) (THIS) PURE; };
如果没有宏展开,Doxygen 会感到困惑,但我们可能不想展开 REFIID
宏,因为它已经被文档化,并且阅读文档的用户在实现接口时应该使用它。
通过在配置文件中设置以下内容
ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES PREDEFINED = "DECLARE_INTERFACE(name)=class name" \ "STDMETHOD(result,name)=virtual result name" \ "PURE= = 0" \ THIS_= \ THIS= \ __cplusplus
我们可以确保正确的结果被提供给 Doxygen 的解析器
/*! A reference to an IID */ #define REFIID /*! The IUnknown interface */ class IUnknown { virtual HRESULT QueryInterface ( REFIID iid, void **ppv) = 0; virtual ULONG AddRef () = 0; virtual ULONG Release () = 0; };
注意,PREDEFINED 标签接受函数式宏定义(如 DECLARE_INTERFACE
)、普通宏替换(如 PURE
和 THIS
)以及普通定义(如 __cplusplus
)。
另请注意,通常由预处理器自动定义的预处理器定义(如 __cplusplus
),必须通过 Doxygen 的解析器手动定义(这样做是因为这些定义通常是平台/编译器特定的)。
在某些情况下,你可能希望将宏名称或函数替换为其他内容,而又不让结果参与进一步的宏替换。你可以通过使用 :=
运算符代替 =
来实现这一点
例如,假设我们有以下代码片段
#define QList QListT class QListT { };
那么让 Doxygen 将其解释为类 QList
的类定义的唯一方法是定义
PREDEFINED = QListT:=QList
这是 Valter Minute 和 Reyes Ponce 提供的一个示例,它有助于 Doxygen 处理 Microsoft 的 ATL 和 MFC 库中的样板代码
PREDEFINED = "DECLARE_INTERFACE(name)=class name" \ "STDMETHOD(result,name)=virtual result name" \ "PURE= = 0" \ THIS_= \ THIS= \ DECLARE_REGISTRY_RESOURCEID=// \ DECLARE_PROTECT_FINAL_CONSTRUCT=// \ "DECLARE_AGGREGATABLE(Class)= " \ "DECLARE_REGISTRY_RESOURCEID(Id)= " \ DECLARE_MESSAGE_MAP= \ BEGIN_MESSAGE_MAP=/* \ END_MESSAGE_MAP=*/// \ BEGIN_COM_MAP=/* \ END_COM_MAP=*/// \ BEGIN_PROP_MAP=/* \ END_PROP_MAP=*/// \ BEGIN_MSG_MAP=/* \ END_MSG_MAP=*/// \ BEGIN_PROPERTY_MAP=/* \ END_PROPERTY_MAP=*/// \ BEGIN_OBJECT_MAP=/* \ END_OBJECT_MAP()=*/// \ DECLARE_VIEW_STATUS=// \ "STDMETHOD(a)=HRESULT a" \ "ATL_NO_VTABLE= " \ "__declspec(a)= " \ BEGIN_CONNECTION_POINT_MAP=/* \ END_CONNECTION_POINT_MAP=*/// \ "DECLARE_DYNAMIC(class)= " \ "IMPLEMENT_DYNAMIC(class1, class2)= " \ "DECLARE_DYNCREATE(class)= " \ "IMPLEMENT_DYNCREATE(class1, class2)= " \ "IMPLEMENT_SERIAL(class1, class2, class3)= " \ "DECLARE_MESSAGE_MAP()= " \ TRY=try \ "CATCH_ALL(e)= catch(...)" \ END_CATCH_ALL= \ "THROW_LAST()= throw"\ "RUNTIME_CLASS(class)=class" \ "MAKEINTRESOURCE(nId)=nId" \ "IMPLEMENT_REGISTER(v, w, x, y, z)= " \ "ASSERT(x)=assert(x)" \ "ASSERT_VALID(x)=assert(x)" \ "TRACE0(x)=printf(x)" \ "OS_ERR(A,B)={ #A, B }" \ __cplusplus \ "DECLARE_OLECREATE(class)= " \ "BEGIN_DISPATCH_MAP(class1, class2)= " \ "BEGIN_INTERFACE_MAP(class1, class2)= " \ "INTERFACE_PART(class, id, name)= " \ "END_INTERFACE_MAP()=" \ "DISP_FUNCTION(class, name, function, result, id)=" \ "END_DISPATCH_MAP()=" \ "IMPLEMENT_OLECREATE2(class, name, id1, id2, id3, id4,\ id5, id6, id7, id8, id9, id10, id11)="
如你所见,Doxygen 的预处理器功能相当强大,但如果你想要更大的灵活性,你可以随时编写一个输入过滤器,并在 INPUT_FILTER 标签或 FILTER_PATTERNS 标签(或 FILTER_SOURCE_PATTERNS 标签)后指定它。
如果你不确定过滤器的效果会如何,你可以按如下方式运行 Doxygen:doxygen -d filteroutput。
如果你不确定 Doxygen 预处理的效果会如何,你可以按如下方式运行 Doxygen
doxygen -d Preprocessor
或者当不需要行号时
doxygen -d Preprocessor -d NoLineno
这将指示 Doxygen 在完成预处理后将输入源转储到标准输出(提示:在配置文件中设置 QUIET = YES
和 WARNINGS = NO
以禁用任何其他输出)。
请注意,并非所有语言都进行预处理。对于使用“C”扫描器(Java、D 和 PHP 除外)、Fortran 文件(仅当扩展名包含至少一个大写字符时)和 VHDL 文件,启用预处理。