Automatic Header File Generation Cay S. Horstmann mdgen Documentation 0. Purpose of the utility This utility, for historical reasons called mdgen (module definition generator), extracts header files from marked up source files. The utility solves one problem: the separate maintenance of header and implementation files. In C++, function and variable declarations and class definitions must be contained in a header file, whereas the implementation of functions and operations must be placed in an implementation file, except of course for inline functions. Making a change means fixing two files. This bothers some programmers a lot, others say `so what'. If maintaining two sets of files isn't a problem for you, then don't use mdgen. If it is, read on. 1. Running mdgen To run mdgen, simply specify the source files you want to scan. mdgen date.cpp str.cpp hw*.cpp This produces (or updates) files date.h, str.h, and hw*.h for all files matching hw*.cpp. 2. The EXPORT tag Unfortunately, mdgen is not smart enough to figure out what features to extract from the source file into the header file. You have to tag all exported features with the string EXPORT. In practice, this is not a great problem--certainly less trouble than switching back and forth between edit windows. EXPORT is defined in setup.h as the empty string and has no influence on the compilation. 3. Global variables and functions Let us start with the easiest case: tagging global variables and functions. Just prefix them with the tag EXPORT. EXPORT int days_per_month[12] = { 31, 28, ..., 31 }; EXPORT istream& operator>>(istream& is, Date& date) { // ... } Then mdgen places the following lines into the header file: extern int days_per_month[12]; extern istream& operator>>(istream& is, Date& date); That is, the variable initializer and function body are ignored, and the declaration is terminated with a semicolon. You only tag the variables and functions that you want to export. As a consistency check, see that all global variables and functions are either tagged as EXPORT or static. You benefit from the automatic extraction in two ways. You don't have to type the declaration twice. And if you make a change in the function definition, file, just run mdgen and let it update the header file for you. 4. Classes Tag classes with EXPORT: EXPORT class Date { public: // ... }; The entire class definition is copied to the header file. You may need to place a class declaration into the header file, either to deal with circular dependencies or to avoid reading an entire header file. Just tag the declaration. EXPORT class iostream; EXPORT class Time; You only tag the class definitions, not the definitions of the operations. 5. Other types Unions, structures, typedef and enumerations are handled just like classes. Tag them with the EXPORT token. 6. Inline functions Inline functions must be copied to the header file since the inline replacements may need to be carried out when compiling other modules. Tag inline functions with EXPORT. EXPORT inline int Date::day() const //˙RETURNS: the day of this date { return _day; } EXPORT inline int Date::month() const //˙RETURNS: the month of this date { return _month; } EXPORT inline int Date::year() const //˙RETURNS: the year of this date { return _year; } 7. Constants The handling of constants in C++ is murky. By default, global constants are static, that is, not visible from other modules. This is to enable the compiler to perform inline replacement on constants rather than using memory lookups. const int DAYS_PER_YEAR = 365; y = d / DAYS_PER_YEAR; // compiles as d / 365 Integer constants can even be used at compile time! const int BUFFER_SIZE = 80; char buffer[BUFFER_SIZE]; This is different from C. In C, constants always occupy memory and they are never evaluated at compile time. Often, the value of such an `inline constant' needs to be visible to other modules during compilation, and the definition must be placed in the header file. In C++, a constant that occupies storage and can be referenced from other files must be declared and defined as extern const. extern const int days_per_month[12] = { 31, 28, ..., 31 }; extern const Complex imag_unit = Complex(0, 1); These are fairly rare. Both cases are handled by mdgen. The tagged definitions EXPORT const int DAYS_PER_YEAR = 365; EXPORT extern const int days_per_month[12] = { 31, 28, ..., 31 }; become const int DAYS_PER_YEAR = 365; extern const int days_per_month[12]; in the header file. 8. Templates Templates of classes, operations, functions and variables must be copied to the header file. Tag them with EXPORT. EXPORT template class Array /*˙PURPOSE: smart array class template RECEIVES: T - any type with copy construction and assignment */ { public: // ... }; EXPORT template< class X > Array::Array(const Array& b) : _low(b._low), _high(b._high) { // ... } 9. Header file include directives To copy an include directive, place a keyword EXPORT on the preceding line. EXPORT #include If the header file doesn't require the contents of iostream.h, except to know that istream and ostream are classes, you can generate a more efficient header file by exporting only the class declarations // no EXPORT #include EXPORT class istream; EXPORT class ostream; 10. Miscellaneous preprocessor features Occasionally, you need to include #ifdef or macros such as DECLARE(X, Y) in a header file. Any code that is enclosed in between #ifndef EXPORT ... #endif block. is copied by mdgen into the header file. 11. Warts Currently, mdgen is implemented as a compiled awk script. The script has evolved for over five years and handles most cases smoothly. However, a few ugly problems remain. Default arguments must be part of the declaration of a function and not repeated in the definition. For operations of a class, this is not a concern since the operation declaration is inside the class and distinct from the definition. But for global functions, a separate declaration must be provided. EXPORT void display(Factory& f, Bool use_color = TRUE) // DON'T { // ... } Separate this into a declaration followed by a definition EXPORT void display(Factory& f, Bool use_color = TRUE); void display(Factory& f, Bool use_color) { // ... } This is dumb but fortunately rare. The mdgen program cannot tell the difference between certain variable definitions and functions. As a consequence, it will not remove the initializer. Consider EXPORT Complex imag_unit(0, 1); // DON'T Even many human readers must look twice to see that it is not a function call. Instead, use EXPORT Complex imag_unit = Complex(0, 1); // OK 12. Double scan protection The header files produced by mdgen are automatically protected against double scanning. The entire file is surrounded by directives #ifndef DATE_H #define DATE_H // ... #endif 13. Error reporting If you make a syntax error in an implementation file that causes a faulty header file to be extracted, the compiler will report the error in the source file, pointing to the actual offending line. The mdgen utility places #line directives in the header file for this purpose. If the compiler complains about syntax errors, fix the errors in the source file and run mdgen again. 15. Interaction with `make' utilities The mdgen utility respects time stamps of header files. If the changes in date.cpp do not require a modification in date.h, the old date.h is not changed. You can simply run mdgen *.cpp to refresh all header files. You need not worry that the `make' or project file triggers a complete recompile. If no change in a header files was detected, it is not touched. You can teach your `make' or project tool a dependency rule to automatically invoke mdgen. For example, Borland `make' accepts the following rule set: .cpp.obj: $(cc) -P $(copts) $< .asm.obj: $(asm) $< .cpp.h: mdgen $< 16. Installation as a tool In Turbo/Borland C++, you can install mdgen as a tool that can be invoked inside the integrated development environment. Follow these steps for version 3. For version 4, the steps are similar. In the "Options|Transfer" dialog, move the cursor to a blank entry and select "Edit". Set the title to "~mdgen", the path to "mdgen", and the command line to "$EDNAME $SAVE CUR". Remember to save your options. To invoke mdgen, hit [Alt+Space] and type "m". The current window is saved and run through mdgen.