プリプロセッシング

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_PREPROCESSINGNOに設定することで、すべてのプリプロセッシングを無効にできます。上記の場合、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など)、通常のマクロ置換(PURETHISなど)、およびプレーンな定義(__cplusplusなど)を受け入れることに注意してください。

また、プリプロセッサによって通常自動的に定義されるプリプロセッサ定義(__cplusplusなど)は、Doxygenのパーサーで手動で定義する必要があることにも注意してください(これは、これらの定義がしばしばプラットフォーム/コンパイラ固有であるためです)。

場合によっては、マクロ名や関数を別のものに置換したいが、その結果をさらにマクロ置換に晒したくないことがあります。これは、=演算子ではなく:=演算子を使用することで実現できます。

例として、次のコード断片があるとします。

#define QList QListT
class QListT
{
};

この場合、DoxygenにこれをQListクラスのクラス定義として解釈させる唯一の方法は、以下を定義することです。

PREDEFINED = QListT:=QList

以下は、Valter MinuteとReyes Ponceが提供した例で、MicrosoftのATLおよびMFCライブラリのボイラープレートコードをDoxygenが処理するのに役立ちます。

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ファイル(拡張子に少なくとも1つの大文字が含まれる場合のみ)、およびVHDLファイルに対してプリプロセッシングが有効になります。

次のセクションに進むか、インデックスに戻ります。