Doxygen documenting with Generic-Text-Mapping
在《Writing libraries with Generic-Text-Mapping》一文裡,我介紹了怎樣撰寫具備 generic text mapping 機制的 library。在該文文末,我提到了目前的缺憾是,沒辦法做出漂亮的 doxygen 文件:
我們可以使用
@copydoc,讓StrLengthA()使用StrLengthW()的那一份文件源碼。但是,使StrLength()為主,並以 function 的方式呈現,筆者就真的找不到解法了。因此,目前筆者所能想出來的,最好的 doxygen 文件寫法如下:/** * Count the length of given string @p str, in number of characters. * * @param[in] str string to count length * @return Returns the length, number of characters, of string @p str. */ size_t StrLengthA(const char* str); /** * @copydoc StrLengthA * @note This is the @c wchar_t version of StrLengthA(). */ size_t StrLengthW(const wchar_t* str); /** * @see Expand to StrLengthA() or StrLengthW() according to current * generic-text-mapping configuration. * @hideinitializer */ #if (defined(UNICODE) || defined(_UNICODE)) # define StrLength StrLengthW #else # define StrLength StrLengthA #endif由於我們根本不需要讓
StrLength()如 macro 般呈現,且 doxygen 會做 C/C++ preprocessing,根據設定,會在文件上顯示StrLength會被展開成為StrLengthW或是StrLengthA,因此使用@hideinitializer把 macro 的右邊隱藏起來。
基本上,這只能算是折衷的辦法,看文件的人,還是必須了解 generic text mapping 的來龍去脈,才能理解 StrLength 這個 macro 為甚麼要這麼說明。而且,StrLength 會被歸類在 define 一類,而不是理解上的 function,這樣會讓人無法按圖索驥,訪知有此函式可用。
最近,我終於摸索出,如何使用 doxygen 做出如 MSDN 那般,能夠漂亮展現 generic text mapping 型函式的文件寫法。這個方法的訣竅在於:Doxygen 可以理解依靠 preprocessor 的 conditional parsing,故我們準備一個假的函式宣告,專門給 doxygen 產生文件即可。
#ifdef DOXYGEN_ONLY /** * Count the length of given string @p str, in number of characters. * * @param[in] str string to count length * @return Returns the length, number of characters, of string @p str. */ size_t StrLength(const TCHAR* str); // fake prototype for doxygen only #else /* DOXYGEN_ONLY */ #if (defined(UNICODE) || defined(_UNICODE)) # define StrLength StrLengthW #else # define StrLength StrLengthA #endif #endif /* DOXYGEN_ONLY */ /** * ASCII version of StrLength(). */ size_t StrLengthA(const char* str); /** * Wide-char version of StrLength(). */ size_t StrLengthW(const wchar_t* str);
然後在 Doxyfile 裡打開 ENABLE_PREPROCESSING,並於 PREDEFINED 定義 DOXYGEN_ONLY,如下:
# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will. # evaluate all C-preprocessor directives found in the sources and include. # files. ENABLE_PREPROCESSING = YES # The PREDEFINED tag can be used to specify one or more macro names that. # are defined before the preprocessor is started (similar to the -D option of. # gcc). The argument of the tag is a list of macros of the form: name. # or name=definition (no spaces). If the definition and the = are. # omitted =1 is assumed. To prevent a macro definition from being. # undefined via #undef or recursively expanded use the := operator. # instead of the = operator. PREDEFINED = DOXYGEN_ONLY
如此一來,當 doxygen 在產生文件的時候,會去抓 size_t StrLength(const TCHAR* str); 這一段,相反地,在真的要 compile 程式時,因為沒有定義 DOXYGEN_ONLY,所以 compiler 看不見這個假 prototype,反而是看到 generic text mapping 用以定義 StrLength 要對應到 StrLengthA 還是 StrLengthW 的部份。
利用這個技巧,我們可以真的產生一個名叫 StrLength 的函式的文件,且其文件是真的寫在 StrLengh() 處,不再有 @copydoc 技法該以誰為主的問題,更避開了 doxygen 處理 @copydoc 時,在產生出來的 function list 裡,會連 @brief 以外的內容都展示出來的 bug。
最後,讓我們再加上一個表格,於 StrLength() 裡,表列各種版本:
#ifdef DOXYGEN_ONLY /** * Count the length of given string @p str, in number of characters. * * @param[in] str string to count length * @return Returns the length, number of characters, of string @p str. * * @remarks Generic-Text Routine Mappings: * <div class="generic_text_mapping_table"> * <table> * <tr> * <th>TCHAR routine</th> * <td>StrLength()</td> * </tr> * <tr> * <th>_UNICODE & _MBCS not defined</th> * <td>StrLengthA()</td> * </tr> * <tr> * <th>_MBCS defined</th> * <td>StrLengthA()</td> * </tr> * <tr> * <th>_UNICODE defined</th> * <td>StrLengthW()</td> * </tr> * </table> * </div> */ size_t StrLength(const TCHAR* str); // fake prototype for doxygen only #else /* DOXYGEN_ONLY */ #if (defined(UNICODE) || defined(_UNICODE)) # define StrLength StrLengthW #else # define StrLength StrLengthA #endif #endif /* DOXYGEN_ONLY */ /** * ASCII version of StrLength(). */ size_t StrLengthA(const char* str); /** * Wide-char version of StrLength(). */ size_t StrLengthW(const wchar_t* str);
呈現出來的效果如下:
補充:
Doxygen 的 DISTRIBUTE_GROUP_DOC 可以讓同一個 member group 的所有函式,共享 group 裡第一個函式的文件。這在處理 function overloading 上,非常的好用,而我發現在處理 generic-text-mapping 上,也很好用。
我們還是需要將假造的 StrLength() 宣告,並隱藏 generic-text-mapping 使用的「依據 _UNICODE 定義與否,#define 不同版本的 StrLength()」的那段 preprocessing code。,先將假造的 StrLength() 宣告擺在最前面,把文件寫在那邊,然後在後面補上 StrLengthA() 與 StrLengthB() 的宣告,但不要加任何文件。
如此一來,StrLengthA() 與 StrLengthW() 就會自動分享 StrLength() 的文件內容。比起寫「ANSI version of StrLength()」還要來的漂亮。
/** @{ */
#ifdef DOXYGEN_ONLY
/**
* Count the length of given string @p str, in number of characters.
*
* @param[in] str string to count length
* @return Returns the length, number of characters, of string @p str.
*
* @remarks Generic-Text Routine Mappings:
* <div class="generic_text_mapping_table">
* <table>
* <tr>
* <th>TCHAR routine</th>
* <td>StrLength()</td>
* </tr>
* <tr>
* <th>_UNICODE & _MBCS not defined</th>
* <td>StrLengthA()</td>
* </tr>
* <tr>
* <th>_MBCS defined</th>
* <td>StrLengthA()</td>
* </tr>
* <tr>
* <th>_UNICODE defined</th>
* <td>StrLengthW()</td>
* </tr>
* </table>
* </div>
*/
size_t StrLength(const TCHAR* str); // fake prototype for doxygen only
#else /* DOXYGEN_ONLY */
#if (defined(UNICODE) || defined(_UNICODE))
# define StrLength StrLengthW
#else
# define StrLength StrLengthA
#endif
#endif /* DOXYGEN_ONLY */
size_t StrLengthA(const char* str);
size_t StrLengthW(const wchar_t* str);
/** @} */
請注意多加的 /** @{ */ 與 /** @} */,這對括號會將裡面的東西,合併成一個 member group。最後呈現出來的效果如下:





Post a Comment