LHeader and LConfig Patterns

As discussed earlier, one form of late binding is compile time binding. This is an abstraction technique that allows you to create different flavors of your project based on compile time settings. While it does add some additional complexity to the structure of your header files and build scripts, it also provides a reliable way to cleanly automate building multiple variants of your binary.

Two patterns that you can implement in your code to facilitate building different versions are LHeader and LConfig.

LHeader

With the Late Header, or LHeader, pattern, you defer which header files are actually included until compile time. In this way, the name bindings don’t occur until compile time. This decoupling makes the module more independent and reusable, and it simplifies the construction of unit tests. The original motivation for creating the LHeader pattern was to create compiler- or platform-independent C code without using #ifdef/#else constructs in the source code files for each different platform that the source code is compiled for. The principle mechanism for this pattern is the C/C++ compiler’s header search path options. The LHeader pattern has four major components to it. Note: The file references are from the PIM repository

  1. When creating a source code file, use a “naked” #include statement that references a header file that does not resolve to any header file in the baseline set of header search file paths. The file that is ultimately included at compile time is responsible for resolving the deferred name bindings in the source file, for example:
#include "colony_map.h" // Note: needs to be a global unique file name
  1. Deferred name bindings are created by defining a preprocessor symbol that maps to another, yet to be resolved, symbol name, for example in the file src\Cpl\Text\strapi.h the binding for the function strncasecmp is deferred.
#include "colony_map.h"
...

/** Same as strcasecmp, but only compares up to 'n' bytes. It has
    the same semantics as strncmp.
    Prototype:
       int strncasecmp(const char *s1, const char *s2, size_t n);
 */
#define strncasecmp strncasecmp_MAP

Another example, this time deferring the definition of the Cpl_System_Mutex_T type.

#include "colony_map.h"
...

/** Defer the definition of the a raw mutex type to the application's
    'platform'
 */
#define Cpl_System_Mutex_T Cpl_System_Mutex_T_MAP
  1. Then on a per-project basis (or a per-unit test basis), a project specific search path is added that allows the partial #include reference to be resolved to a project-specific instance of the header file name. For example, when building with the Visual Studio compiler, you can specify the compiler header search path using the -I command-line option. Here are the compiler header search paths that are used for building the example thermostat application simulator. In this example, project\Storm\Thermostat\simulation\windows\vc12 is the build directory.

-I\_workspaces\zoe\pim\src
-I\_workspaces\zoe\pim\projects\Storm\Thermostat\simulation\windows\vc12
  1. A per-project header file is created that resolves the deferred name bindings. Here is an example of the colony_map.h file located in the project\Storm\Thermostat\simulation\windows\vc12 directory. The file’s content is:
// Cpl::Text strapi mapping
#include "Cpl/Text/_mappings/_vc12/strapi.h"

// Cpl::System mappings
#include "Cpl/System/Win32/mappings_.h"

The #include statements in the colony_map.h header file pull in specific mappings that resolve the compile time bindings for the Cpl::Text and Cpl::System interfaces. Here is a code snippet from the src\Cpl\Text\strapi.h file that resolves the mapping for the strncasecmp function when using building with the Visual Studio compiler

#define strcasecmp_MAP _stricmp

And here is the code snippet from the Cpl/System/Win32/mappings_.h file that resolves the mapping for the Cpl_System_Mutex_T  type when building with the Visual Studio compiler.

/// Win32 Mapping
#define Cpl_System_Mutex_T_MAP                  CRITICAL_SECTION

LConfig

The Late Config, or LConfig, pattern is a specialized case of the LHeader pattern that is used exclusively for configuration. The LConfig pattern provides for project-specific header files that contain preprocessor directives or symbol definitions that customize the default behavior of the source code.

The LConfig pattern uses a globally unique header file name, and it relies on a per-project unique header search path to select a project-specific configuration. That is, it uses the same basic mechanisms as the LHeader pattern; however, LConfig is not used for resolving deferred function and type name bindings. Configuration settings are settings that override defaults or magic constants and are used to enable or disable conditionally compiled code. The LConfig pattern uses a separate/different the reserved header file name than the LHeader pattern.

#include "colony_config.h" // Note: needs to be a global unique file name

Here is an example code snippet for a numeric constant that can be changed at compile time.

#include "colony_core.h"
...

/** Specifies the default value used for the application exit code when
    terminating due to a fatal error.
 */
#ifndef OPTION_CPL_SYSTEM_FATAL_ERROR_EXIT_CODE
#define OPTION_CPL_SYSTEM_FATAL_ERROR_EXIT_CODE     2
#endif
...

Here is an example code snippet for specifying a preprocessor directive at compile time.

#include "colony_config.h" // LConfig pattern reserved header file
...
// Defined in colony_config.h to enable tracing
#ifdef USE_CPL_SYSTEM_TRACE
...
#define CPL_SYSTEM_TRACE_MSG(sect, var_args) do { ... } while(0)
...
#else
...
#define CPL_SYSTEM_TRACE_MSG(sect, var_args)
...
#endif // end USE_CPL_SYSTEM_TRACE

Finally, a code snippet for a project specific colony_config.h header file located in project’s build directory.

#ifndef COLONY_CONFIG_H
#define COLONY_CONFIG_H

// Override default error code to help with unit testing
#define OPTION_CPL_SYSTEM_FATAL_ERROR_EXIT_CODE  99

// Enable tracing (for debugging my unit test)
#define USE_CPL_SYSTEM_TRACE

#endif

Note: You can achieve the same effect of the LConfig pattern (or for that matter, the LHeader pattern as well) without using a project specific header files (such as colony_config.h) by specifying the preprocessor symbols in your make/build scripts. IME I have found that it is easier to maintain/manage the project specific symbols/definitions outside of the build scripts, especially when there are many symbols and/or when sharing a set of common definitions across multiple projects.

Caveat Implementor

The LHeader pattern works well most of the time. Where it breaks down is when interface A defers a type definition using the LHeader pattern and interface B also defers a type definition using the LHeader pattern and interface B has a dependency on interface A. This use case results in a cyclic header include scenario, and the compile will fail. If you are using C++, this use case typically does not occur. However, when using C code, you will run into it—though it is not a frequent occurrence. When this use case is encountered in either C++ or C, the following constraints are imposed:

  1. The project-specific reserved header file (e.g., colony_map.h) shall only contain #include statements to other header files. Furthermore, this reserved header file (e.g., colony_map.h) shall not have a header latch (i.e., no #ifndef MY_HEADER_FILE construct at the top or bottom of the header file).
  2. The header files, which are included by the project-specific colony_map.h header file and which resolve the name bindings, shall have an additional symbol check in their header latch. The additional symbol check is for the header latch symbol of the module file that originally declared the name whose binding is being deferred. For example, the extra symbol check for the src/Cpl/System/Win32/mappings.h file would be
// Header latch symbol from the Mutex interface
#ifdef Cpl_System_Mutex_h_

// Traditional header latch for my file
#ifndef Cpl_System_Win32_mappings_h_
#define Cpl_System_Win32_mappings_h_

...

#endif // end header latch
#endif // end interface latch

The conventions described above are only required when there are nested deferred typedefs. In the example code, the actual src/Cpl/System/Win32/mappings.h header file does not use the extra interface latch because none of the Cpl::System interfaces have nested deferred typedefs. Nevertheless, it is recommended that you always follow the “no header latch in the reserved header file” rule. It has a minimal downside, and if you do find yourself with nested deferred typedefs, you won’t get frustrated with failed compiles because you forgot to remove the header latch in the reserved header file. Diagnosing compile time failures due to cyclic header #include statements is usually very frustrating; it is something you want to avoid.

Note: This caveat does not apply to the LConfig pattern since the LConfig pattern is only used to define magic constants and preprocessor directives for conditionally compiled code.