Makro-C-Objekte in Doxygen
« | 25 Jun 2022 | »Wenn ich meine selbstgestrickten Makros einsetze, um
V-Table struct
s
generieren zu lassen und anschließend C-Klassen struct
s, 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 jastruct
nutzen müssen, aber das eigentlich von der IDL stammendeinterface
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.