Makro-C-Objekte in Doxygen

Wenn ich meine selbstgestrickten Makros einsetze, um V-Table structs generieren zu lassen und anschließend C-Klassen structs, dann schreibt Doxygen ziemlich wirres Zeug.

Ähnlich ergeht es manch alten COM Projekten in C, die ebenfalls mit Makros arbeiten (die mir ja auch als Inspiration gedient haben).

Wie bekommt man also eine Doxygen-konforme Lösung hin?


Wie Microsoft’s COM in reinem C Code aussah, hat Raymond Chan bereits super dokumentiert

Im GATE Projekt beginnt alles mit dem “gate_object” Interface und das wird aus “ähnlichen” Makros zusammengebaut. Alle Ableitungen müssen dabei das Interface der Basisklassen nochmals exakt nachstellen, damit die entstehende V-Table “kompatibel” bleibt:

 1GATE_INTERFACE(gate_object)
 2{
 3  GATE_METHOD0(char const*, get_interface_name);
 4  GATE_METHOD0(void,        release);
 5  GATE_METHOD0(int,         retain);
 6};
 7
 8GATE_INTERFACE(gate_cloneable)
 9{
10  /* extends: gate_object */
11  GATE_METHOD0(char const*, get_interface_name);
12  GATE_METHOD0(void,        release);
13  GATE_METHOD0(int,         retain);
14
15  /* clonable methods: */
16  GATE_METHOD1(gate_result_t, clone, gate_cloneable_t** clone);
17};

Daraus wird für ein gate_cloneable Interface in etwa folgender Code generiert:

 1typedef struct
 2{
 3  char const* (*get_interface_name)(void* thisptr);
 4  void        (*release)           (void* thisptr);
 5  int         (*retain)            (void* thisptr);
 6
 7  gate_result_t (*clone)(void* thisptr, gate_cloneable_t** clone);
 8} gate_cloneable_vtbl;
 9
10typedef struct
11{
12  gate_cloneable_vtbl const* vtbl;
13} gate_cloneable_t;

Doxygen kann jedoch die etwas komplexeren Makros im Hintergrund leider nicht alle Auflösen und somit kann ich leider nicht einfach vor GATE_INTERFACE() oder vor GATE_METHOD() meine Doku schreiben.

Lösung: Pseudo-Klasse in Doxygen nachbauen

Es gibt zwar ein paar Tricks, um den Doxygen Parser zu beeinflussen um ihm neue Vokabel beizubringen (für COM existieren solche Zusätze), doch ich hätte gerne eine Lösung, die “Out of the box” funktioniert.

Und die sieht dann so aus:

 1/**
 2 * @interface gate_object_t
 3 * @brief     Base type of all GATE C objects
 4 */
 5
 6/**
 7 * @fn       char const* get_interface_name()
 8 * @memberof gate_object_t
 9 * @brief    Returns on object's interface path name
10 * @return   Static string describing the interface.
11 *
12 * @fn       void release()
13 * @memberof gate_object_t
14 * @brief    Decrease ref-counter, releases object when count == 0
15 *
16 * @fn       int retain()
17 * @memberof gate_object_t
18 * @brief    Increase object's reference counter
19 * @return   Returns the current reference counter value
20 */
21
22
23/**
24 * @interface  gate_cloneable_t
25 * @implements gate_object_t
26 * @brief      Base interface for all objects that can be cloned.
27 */
28
29/**
30 * @fn         gate_result_t clone(gate_cloneable_t** clone)
31 * @memberof   gate_cloneable_t
32 * @brief      Creates a new object by cloning this object
33 * @param[out] ptrclone receives the new cloned object's pointer
34 * @return     GATE_RESULT_* result code
35 */

Folgendes habe ich gelernt:

  • Der Doxygen-Kommentar Block sollte nach dem Macro-Block folgen. Steht er davor, wird GATE_INTERFACE() als Makro oder Funktion erkannt, die dann auch ohne Bedeutung in der Doku steht.
  • Wichtig ist auch, dass man zuerst einen Kommentarblock schreibt, der die Klasse deklariert und anschließend einen neuen Kommentarblock für die Funktionen anlegt.
    Wenn man beides in einen Block zusammenfügt kommt keine schöne Doku dabei heraus.
  • @memberof nach einem @fn markiert die Funktion als zugehörig zur angegebenen @struct, @class oder @interface.
    (Eigentlich hätte ich ja struct nutzen müssen, aber das eigentlich von der IDL stammende interface benennt dann auch in der Doku die Strukturen als “Interface”).
  • Mit @implements werden Vererbungshierarchien definiert, die man dann im generierten Doxygen-HTML auch in einem Graphen durchklicken kann.

Fazit

Naja, ich sehe es jetzt mal als annehmbaren Workaround.
Alle anderen Spielereien mit Doxygen zwischen den Makros haben die generierte HTML Doku nur vernudelt.

Doch mit der Lösung habe ich jetzt endlich etwas funktionierendes, mit dem ich meine Interface Definitionen auch verständlich dokumentieren kann.

Denn eigentlich war dieser offene Punkt einer der Gründe, warum ich seit zwei Jahren die Dokumentation des Projektes immer wieder aufgeschoben habe. Bisher habe ich nur sporadisch freie C-Funktionen mit Doxygen Kommentaren versehen.

Das Schlimme ist … jetzt muss ich meinen hunderten Funktionen auch wirklich mal eine verständliche Bedeutung zuordnen.

Doch das ist auch gut … denn damit erfolgt implizit auch ein Code-Review.