4.4.4 Software components

The last major piece of the puzzle are the software components themselves. The most reasonable way of implementing components is to piggyback on shared libraries (dynamically linked libraries), that already provide many of the services needed by components.

Most software is implicitly linked with shared libraries (also known as “load-time dynamic linking”). A program declaratively states which libraries it depends on, and the dynamic linker of the operating system loads the dependent libraries into the address space of the program at load-time, that is, before the program starts executing. As a shared library is potentially loaded at an address which is not known at compile-time, a runtime structure, called a jump table, is often used that holds the addresses to a shared library’s exported functions (Hunt and Scott 1999). It is initialized by the dynamic linker at load-time, and is consulted before functions in the shared library are called.1

Functions that realize object operations in this chapter are not exported—indeed, as they are declared static, their symbol names are not even visible outside their compilation units. Instead, they are accessed through dispatch tables. When a class is put in a shared library, dispatch tables do double duty as jump tables, as well as serving their traditional role of decoupling implementation from interface.

Shared libraries housing components should export one function though, one that makes it possible to instantiate objects. This function should take the runtime name of a class as an argument, and return a factory object capable of instantiating this class. Components must be loaded using explicit linking (also known as “runtime dynamic linking”), as all components use the same symbol name for this function. When a shared library is loaded explicitly, operating systems universally return a handle that can be used to refer to the library at runtime.2 In this context, this handle can be considered the runtime identity of the component, which makes it possible to differentiate between components at runtime.

Components should also provide version information on themselves, and state what other components they depend on. The runtime library of the component model discussed here should take this information into account when loading a component, to make sure that all dependencies are met.

With that, the broad outlines of a component model implementation have been sketched.

Footnotes

  1. Some systems, notably Windows, modify the executable code directly instead of using jump tables.
  2. POSIX-compliant systems, such as Linux and other Unix-like systems, provide the dlopen(), dlclose() and dlsym() functions to link in shared libraries explicitly at runtime (dlopen() and dlclose() load and unload a shared library, respectively, and dlsym() returns the address of the function associated with a given symbol name). The equivalent functions under Windows are LoadLibrary(), FreeLibrary() and GetProcAddress(), which roughly correspond to the aforementioned POSIX functions, in that order.