用作 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 文件都启用了预处理。