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 文件启用。