gwyn@smoke.brl.mil (Doug Gwyn) (11/10/90)
In article <24964@adm.BRL.MIL> nick@spider.co.uk (Nick Felisiak) writes: >Ideally, the only thing visible by name is the streamtab structure which >defines the entry points to the driver. In practice, if a driver is >constructed from more than one source file, a potentially large number >of names remain visible simply because there is no type of name which >disappears after an 'ld -r' used to produce the single driver object file. This is a nice example of a real-world problem with the single, flat external identifier name space. The "ld"/COFF hackery attempts to solve these problems by in effect introducing levels into the name space. There is another approach, using standard C facilities, that we have used to good effect in a large project where tight control over name space was a major software engineering consideration. A project-wide header file describes the SOLE allowed externally- visible object for any of numerous potential "methods" (each developed according to project rules, but without knowledge of other "methods"): /* <Dd.h> -- MUVES "Dd" (data dependencies) package definitions This header is part of the "universal" part of the project, available for use by all developers of methods. DdEntry is a definition for the structure type used to package the sole entry point to a method. */ /* ... */ typedef struct { pointer method; /* -> private method data */ int info_needed; /* request flags for RrShoot() */ bool ctx_dependent; /* FRF depends on context */ float default_frf; /* unhit component's FRF value */ bool (*meth_init)( const char *, const char * ); bool (*view_init)( DqNode * ); bool (*shot_init)( const RrRay * ); bool (*cont_init)( const char *, const char * ); void (*meth_term)( void ); void (*view_term)( void ); void (*shot_term)( void ); void (*cont_term)( void ); } DdEntry; /* DdApprox is a copy of the entry-point structure that contains method-specific initialization/termination function pointers and globally-accessible data for the method. It is set to the proper value at run time after the user has specified which method to use. This is used by MUVES code outside all method implementations when making standard "requests" to the currently selected method. */ extern DdEntry DdApprox; Then, each "method" is allowed to use the ONE global symbol "DdXXX", where "XXX" is actually replaced by the method's name. /* compart.h -- "compart" method private definitions */ #include <Dd.h> /* for DdEntry */ /* ... */ /* Private external functions and data must be accessed via the sole external hook for the "compart" method, Ddcompart, which contains a method-specific pointer as its first member. In the "compart" method's case, this is a pointer to an XxEntry structure, which contains XxBypass and pointers to functions defined in the method's support module compart.c. (There are numerous other separate modules implementing the bulk of the method. They communicate with each other solely through the hooks provided in Ddcompart.) Note that in code for this method we can refer directly to Ddcompart; no need to go through the copy in DdApprox since we KNOW what method must be in effect for all code using this "compart" method-private header. */ typedef struct { bool bypass; /* special testing flag */ /* The following are used by various "compart" modules: */ bool (*hit)( RrTrace *trace ); /* ... */ } XxEntry; /* Ddcompart is the structure that contains shared globally- accessible data and entry points for the "compart" method. */ extern DdEntry Ddcompart; /* bool XxBypass XxBypass is a flag that is set to true during the module test programs, in order to disable use of "Dd" package functions. */ #define XxBypass ((XxEntry*)Ddcompart.method)->bypass #ifndef XxNOFNDEFS /* (define XxNOFNDEFS for compart.c only) */ /* bool XxHit( RrTrace *trace ) XxHit() records a true hit flag for the current component (indicating that the component was present on the threat path and was perforated) and returns true upon succcess. If XxHit() fails, false is returned. */ #define XxHit ((XxEntry*)Ddcompart.method)->hit /*...*/ #endif /* XxNOFNDEFS */ Identifiers beginning with "Xx" are guaranteed to not be used by any of the standard project headers (apart from private method-specific headers), so we can be sure that symbols starting with "Xx" will not collide with anything within a single translation unit, as used here. Note that there are NO external identifiers starting with "Xx". The "compart" method's support functions corresponding to the preceding header are all contained in: /* compart.c -- "compart" method shared data and functions */ #include <Dd.h> /* ... */ #define XxNOFNDEFS /* permit definition of "Xx" functions below */ #include "compart.h" #undef XxNOFNDEFS /* XxHit() records a true hit flag for the current component (indicating that the component was present on the threat path and was perforated) and returns true upon succcess. If XxHit() fails, false is returned. */ static bool XxHit( RrTrace *trace ) { /* ... */ return true; } /* Dd-invoked global initialization and termination functions: */ /* XxContInit() is the per-context Dd init function which is called by AtUtility(). This function returns true unless an error occurs. */ static bool XxContInit( const char *mission, const char *envir ) { /* ... */ return true; } /* ... */ /* Private external functions and data must be accessed via the sole external hook for the approximation method, Ddcompart, which contains a method-specific pointer as its first member. In our case, this is a pointer to an XxEntry structure, which contains XxBypass and pointers to functions defined in compart.c. */ static XxEntry XxItems = { false, /* XxBypass lives here! */ XxHit, /* ... */ }; /* Ddcompart is the structure that contains globally-accessible data and entry points for the "compart" method, as described previously. */ DdEntry Ddcompart = /* "compart"-specific hook for DdApprox */ { (pointer)&XxItems, /* method's private pointer */ RrLOCATION | RrNORMAL, /* flags for RrShoot() */ true, /* FRF depends on context */ 1.0, /* FRF for unhit component (LOF=0) */ XxMethInit, /* approx-method initialization func */ NULL, /* per-view initialization func */ NULL, /* per-shot initialization func */ XxContInit, /* per-context initialization func */ XxMethTerm, /* approx-method termination func */ NULL, /* per-view termination func */ NULL, /* per-shot termination func */ NULL /* per-context termination func */ }; The actual MUVES code is considerably more elaborate that the streamlined extracts I have provided here. My point in posting this is to illustrate a practical application of the idea of encapsulating multiple items within a single (possibly hierachically-structured, as in this example) externally- visible object, so that a large number of items can be accessed without using up a large number of external identifiers. A MUVES "method" may be implemented as many thousands of lines of source code split among hundreds of separate source files, yet only a single external identifier is needed for the entire method.