In this section, the decomposition of program portions into modules and interfaces is examined. Different organizations are studied.
A large program or library is usually divided into modules. Each module offers a number of variables, procedures and type definitions to be used from the outside; these are called the interface exported by the module. A module needing information provided by the interface of another module will be called the client of this other module. Some languages, like Modula-3, allow modules to divide their exported information into several interfaces; for example, one interface might contain the information required by almost every client while a second interface may contain more specialized information useful to only a few clients.
The interface should contain only the pieces of information required by client modules. The type definitions and procedures only used locally within the module should be kept out of the interface to avoid diluting the useful information present in the interface. This is a form of what is often called information hiding or encapsulation.
The programmer needs to know from the interface the name and arguments of procedures, and the name and public portions of data types and variables. Indeed, private fields within exported data types are only used from within the exporting module and need not be known by clients. Similarly, the exact address of procedures within a module are not useful to the programmer. The compiler and linker, however, need to know the offset of each field within a data type and the address of each exported procedure and variable.
On virtually every system, the addresses of procedures and variables are stored by the compiler in the object file and used by the linker. The offset of data fields, however, must often be known to the compiler. In many languages, like in C or C++, the private portions of type definitions must be included in the interface to let the compiler compute the offset of the public data fields. Other languages, like Modula-3, keep the interfaces clean by having the compiler output the field offsets into a separate file, used when compiling client modules.
There may be redundancy in declaring exported procedures in the interface and then implementing them in the module. Some programming environments let the programmer flag the exported items in the module and offer tools that automatically extract these items and produce the exported interface.
While most structured languages, (Modula, ADA), have explicit mechanisms to export and import interfaces, some have none and obtain similar functionality through a macro pre-processor. Indeed, in C or C++, a declaration is shared by several modules simply by putting it in a separate file included by several modules. While using an existing tool (macro pre-processor) instead of adding to the language is interesting, there are some very serious problems with this.
The macro pre-processor has its own language and does not understand the program semantics. For example, a missing semi-colon in an included file will not be noticed until the following statement in the including file and the compiler may have a hard time reporting the error location in a meaningful way. More importantly, the effect of the textual inclusion depends on the pre-processor state. Therefore, if file A includes files B and C and file B also includes C, the pre-processor cannot decide that including C the second time is useless because the macro pre-processor state may have changed and macros within C may produce a different result the second time.
The example below shows portions of a C++ program with associated include files serving as exported interfaces. The private portions of data types must be found by the compiler in the interface. Some of the interfaces will be included several times needlessly.
File Main.c #include <stdio.h> #include <iostream.h> #include "Second.h" main() { ... routine(a,b); }
File Second.c #include "Second.h" void routine(int a, Obj b) { ... } void local_routine() { ... }
File Second.h #include <iostream.h> class Obj { private: int a; char *b; public: int access(); void set(int i); ... }; void routine(int a, Obj b); ...
In the second example, a tool automatically exports all items marked as export. The compiler can be told to ignore the export keyword by defining it as a macro that produces the empty string. Thus, the programmer only writes the Second.c file and the Second.h file is generated automatically.
File Main.c #include <stdio.h> #include <iostream.h> #include "Second.h" main() { ... routine(a,b); }
File Second.c export #include <iostream.h> export class Obj { private: int a; char *b; public: int access(); void set(int i); ... }; export void routine(int a, Obj b) { ... } void local_routine() { ... }
In the third example, an equivalent Modula-3 program portion is shown. Interfaces and Modules are defined in the language and it is possible to declare in the interface only the public portion of data types.
File Main.m3 MODULE Main; IMPORT Stdio, Second; BEGIN ... Second.Routine(a,b); END Main.
File Second.m3 MODULE Second; IMPORT Stdio; REVEAL Obj = Public OBJECT a: INTEGER; b: TEXT; METHODS END; PROCEDURE Routine(a: INTEGER; b: Obj) = BEGIN ... END Routine; PROCEDURE Local_routine() = BEGIN ... END Local_routine; BEGIN END Second.
File Second.i3 INTERFACE Second; IMPORT Stdio; TYPE Obj <: Public; Public = OBJECT METHODS access(): INTEGER; set(i: INTEGER); ... END; PROCEDURE Routine(a: INTEGER; b: Obj); ...