Draft Proposal for Dynamic Libraries in C++ (Revision 1)

Document number:   N1496 = 03-0079
Date:   September 22, 2003
Project:   Programming Language C++
Reference:   ISO/IEC IS 14882:1998(E)
Reply to:   Pete Becker
  Dinkumware, Ltd.
  petebecker@acm.org


Changes from n1428:


Overview · Dynamic Libraries and Application Architecture · Use Cases · Design Principles · Impact on the Standard · Proposed Text · Bibliography


Overview

Many operating systems today support separating an application into multiple components, referred to in this paper as dynamic libraries, that are gathered together by the operating system when the application runs. Deferring the final assembly of the application until runtime provides more flexibility for application updates and simplifies addition of code modules that were not part of the application at the time it was compiled. Use of dynamic libraries can also reduce system memory requirements when multiple applications use the same code, by only requiring one copy of the common code to be held in memory instead of requiring a separate copy for each application.

The C++ Standard has no explicit provisions for creating applications that use dynamic libraries. There are a few requirements that were deliberately written to avoid precluding use of dynamic libraries, but in general an application that uses dynamic libraries cannot be written entirely in standard C++.

The terminology, the compiler and linker mechanisms, and the semantic rules for dynamic libraries vary widely from system to system. In particular, it is difficult to write applications that use dynamic libraries and are portable between Windows and UNIX, even if those applications use only standard C++ except where non-standard code is required for dynamic libraries.

This paper discusses the issues presented in adding support for dynamic libraries to standard C++ and it makes specific recommendations for changes to the C++ standard for that support. These recommendations are at present incomplete, and the parts that are presented will undoubtedly be changed extensively as a result of future discussions. They are intended to provide a starting point and a framework for changes needed to support dynamic libraries in standard C++.

Dynamic Libraries and Application Architecture

Dynamic libraries are an implementation technique that supports two somewhat different application architecture features. First, by deferring linking of some source identifiers until runtime, they can make distribution of updated application code simpler, since only the affected dynamic libraries need to be replaced and not the entire application. Second, by supporting loading of libraries identified at runtime (e.g. through dlsym, LoadLibrary, etc.) they make it easier for an application to support user-supplied extensions, through user-written or third-party add-on libraries, commonly known as "plug-ins". This paper currently only addresses deferred linking; loadable libraries are important, however, and their omission is temporary.

Use Cases

There are four situations that we need to consider in designing support for dynamic libraries in C++:

Design Principles

Several principles should be considered in designing C++ language support for programs that use dynamic libraries. These principles sometimes conflict.

Language support for dynamic libraries should be easy to use. The syntax should be simple and intuitive; converting existing applications should, in most cases, be straightforward; the constraints imposed on applications that use dynamic libraries should be easy to understand.

Applications that use dynamic libraries should be portable. This doesn't mean that code can simply be recompiled and expected to work. It means that most platform differences should be masked by the language support, that most differences that aren't masked can be identified when recompiling, and that differences that aren't identified when recompiling can be fairly easily diagnosed, either by source code analysis or by debugging.

Portable C++ applications that use dynamic libraries should be as efficient as applications written in other languages and as applications that use native support for dynamic libraries directly. Minor differences in speed and size are acceptable; major ones are not.

The use of dynamic libraries should be transparent. Making the obvious syntactic changes needed to convert a statically linked application to a dynamically linked one should not introduce subtle semantic differences. (For example, this means that changing a declaration of an external name to a declaration of a shared name should not affect overloading).

Dynamic libraries should support information hiding. Developers of dynamic libraries should be able to control which parts of a dynamic library are available to users and which parts are not. This makes it easier to replace the implementation of a dynamic library without affecting applications that use it.

The transition to portable C++ should be easy. The semantics of dynamic library use should be as close to the semantics of native implementations as possible.

Impact on the Standard

Definition of a Program

A standard C++ program today is a set of translation units, constrained by the one definition rule, compiled separately and linked together. To allow for dynamic libraries an additional layer is needed between translation units and programs. This paper uses the term linkage unit1 for this layer. Thus, a program is a set of linkage units that are compiled and linked separately, constrained by the one definition rule, and linked together when the program is run. A linkage unit is a set of translation units, constrained by the one definition rule, compiled separately and linked together.

This expanded definition of a program is reflected in the recommendation to add a clause entitled The C++ Program Model to the standard's General clause, and a change to the current Phases of Translation.

The program model also introduces the notion of tentative resolution, requiring that all shared references be resolved at the time that a linkage unit is statically linked. This is done by linking against a library or libraries that provide definitions for all of these symbols2.

Open issue. We need to discuss this in Kona. The two extremes are:

Further, the program model introduces the notion of a linkage unit identifier3. Linkage unit identifiers identify the linkage units that were used to tentatively resolve shared references when a linkage unit was statically linked. If a shared identifier that is used in the application is present at runtime in a linkage unit with a different identifier than the one to which it was tentatively resolved the behavior of the program is undefined. This permits replacement of the linkage unit that was used during the static link with a different implementation (typically a newer version) at runtime, but does not permit moving functions and data objects to different linkage units4.

Open issue. Depending on what we decide about the preceding issue, this may be moot. The purpose of this circumlocution was to permit replacement of mylibrary.1 with mylibrary.2. The linkage unit identifier for both libraries would have to be the same. That amounts to an assertion by the library provider that these libraries do "the same thing".

Shared Linkage

Entities within a linkage unit whose names can be referred to from another linkage unit have shared linkage5. This requires a straightforward change to the standard's discussion of Linkage. This change also means that names with external linkage can only be referred to by other code in the same linkage unit.

Extension of the One Definition Rule

The recommended changes to the One Definition Rule are that multiple definitions of the same function or object that would currently be allowed in a program be allowed within in a linkage unit, and that shared functions and objects may not be defined more than once6.

Declaration Syntax

There are three possible approaches to specifying which names in a translation unit refer to entities with shared linkage. First, names with external linkage can be non-shared by default, and the programmer would have to explicitly identify names that are to have shared linkage. This is the model that Windows programmers are familiar with. Second, names with external linkage can be shared by default, and the programmer would have to explicitly identify names that are not to have shared linkage. This is similar to the model that UNIX programmers are familiar with. Third, it can be implementation-defined which of the two preceeding models applies. This minimizes the required changes to existing code.

This paper does not yet recommend syntax for specifying which names have shared linkage. However, it seems clear that the syntax for specifying which names have shared linkage should have the following properties7:

Type Identification and Other Compiler-Generated Meta-Data

The changes suggested so far do not address the issue of type identity across linkage units. For example, in order for code in one linkage unit to catch an exception that was thrown by code in a different linkage unit, the type of the thrown object must be recognized in the code that handles the catch clause. This means that the two linkage units must have the same notion of the exception's type. On the other hand, two linkage units that were developed independently may unintentionally use the same type name. Such independent use of the same name for two distinct types should not lead to program failure.

In C++ today, a class type with external linkage must have the "same" definition in every translation unit that uses it. It's clear that this rule should apply to type definitions within a linkage unit. However, in a dynamically linked application the actual components are determined at runtime, so the application developer cannot control what types are defined within different linkage units. In this setting it's not clear what the rule should be. I'm uncomfortable with the notion of shared and non-shared types, but maybe that's what we need.

Construction and Destruction of Static Objects, atexit

In the absence of loadable libraries (see below) the current rules appear adequate.

Support for Loadable Libraries

Next draft?

Proposed Text

Program Model

Remove paragraph 1 of [basic.link] (3.5/1) which currently reads:

A program consists of one or more translation units (clause 2) linked together. A translation unit consists of a sequence of declarations.
    translation-unit:
        declaration-seqopt

Add new clause [intro.program.model] immediately preceding [intro.execution] (1.9):

The C++ Program Model
A program consists of one or more linkage units linked together at runtime. A linkage unit consists of one or more translation units (clause 2) linked together when the application is built. A translation unit consists of a sequence of declarations.
    translation-unit:
        declaration-seqopt
When translation units are linked to form a linkage unit they may contain references to shared entities that are not defined by any translation unit in the linkage unit being linked. Such references shall be tentatively resolved by linking to a file or files with implementation-defined type specifying which linkage unit or units define each of those shared entities.
Every linkage unit has a linkage unit identifier whose form is implementation-defined.
A program begins execution in the linkage unit that defines the main function. Prior to calling a shared function or accessing a shared object that is not defined in the currently-executing linkage unit the program loader must finally resolve references to that function or object by loading the linkage unit that defines it8. The linkage unit that defines each such reference must have the same linkage unit identifier as the linkage unit to which the reference was tentatively resolved. [Note: this does not require that the linkage unit to which the reference is finally resolved be identical to the one to which it was tentatively resolved. This allows use of replacement dynamic libraries provided that all symbols tentatively resolved to a particular dynamic library must also be finally resolved to the corresponding replacement library. ]
[Note: the two steps of tentative resolution and final resolution can be thought of as jointly defining deferred linking. This term is preferable to the more common term "dynamic linking" because it more strongly suggests the similarity between the name resolution done at final resolution and the name resolution done in static linking. ]

Phases of Translation

Change the second paragraph of [lex] (2/2) from:

[Note: previously translated translation units and instantiation units can be preserved individually or in libraries. The separate translation units of a program communicate (3.5) by (for example) calls to functions whose identifiers have external linkage, manipulation of objects whose identifiers have external linkage, or manipulation of data files. Translation units can be separately translated and then later linked to produce an executable program. (3.5). ]

to:

[Note: previously translated translation units and instantiation units can be preserved individually, in libraries, or in linkage units. The separate translation units of a linkage unit communicate (3.5) by (for example) calls to functions whose identifiers have shared or external linkage, manipulation of objects whose identifiers have shared or external linkage, or manipulation of data files. The separate linkage units of a program communicate (3.5) by (for example) calls to functions whose identifiers have shared linkage, manipulation of objects whose identifiers have shared linkage, or manipulation of data files. Translation units can be separately translated and then later linked to produce a linkage unit. ]

Change paragraph nine of [lex.phases] (2.1/9) from:

All external object and function references are resolved. Library components are linked to satisfy external references to functions and objects not defined in the current translation. All such translator output is collected into a program image which contains information needed for execution in its execution environment.

to:

All external object and function references are resolved and all shared object and function references are resolved or tentatively resolved. Library components are linked to satisfy external references to functions and objects not defined in the current translation unit and to satisfy shared references to functions and objects not defined in the current linkage unit. All such translator output is collected into a partial executable image which contains information needed for execution in its execution environment.

Add new final paragraph to [lex.phases] (2.1/10):

All shared object and function references that were tentatively resolved when each linkage unit used by the progam was linked are finally resolved. [Note: this final resolution will typically occur each time the program is executed. ]

One Definition Rule

Change paragraph three of [basic.def.odr] (3.2/3) from:

Every program shall contain exactly one definition of every non-inline function or object that is used in that program; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8). An inline function shall be defined in every translation unit in which it is used.

to:

Every linkage unit shall contain exactly one definition of every non-inline function or object that is used in that linkage unit; no diagnostic required. The definition can appear explicitly in the linkage unit, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8). An inline function shall be defined in every translation unit in which it is used.
Every program shall contain exactly one definition of every shared function or object that is used in that program; no diagnostic required. The definition must appear explicitly in one of the linkage units that make up the program. [Note: this implies that inline functions and implicit template instantiations cannot, in general, be shared. ]

Linkage

Replace the first bullet item of the second paragraph from [basic.link] (3.5/2):

When a name has external linkage, the entity it denotes can be referred to by names from scopes of other translation units or from other scopes of the same translation unit.

with two bullet items:

When a name has shared linkage, the entity it denotes can be referred to by names from scopes of other linkage units, from scopes of other translation units of the same linkage unit, or from other scopes of the same translation unit.
When a name has external linkage, the entity it denotes can be referred to by names from scopes of other translation units of the same linkage unit or from other scopes of the same translation unit.

Bibliography


1. The term "linkage unit" was chosen to suggest an analogy to a translation unit and to emphasize that the use of dynamic libraries depends on runtime linking. However, "linkage" has a specific meaning in the C++ standard, so "linkage unit" might confuse people. Will this be a problem? If so, suggestions for a different term are welcome.

2. For example, under UNIX this typically means linking to the shared objects that define those symbols; under Windows this typically means linking to import libraries associated with the DLLs that define those symbols.

3. Under UNIX this would typically be the name of the shared object or the name of a link to the shared object; under Windows this is typically the name of the DLL.

4. This is the approach that Windows takes. Another possibility is the UNIX approach, where the linkage unit that defines a function or data object at runtime can be distinct from the one that defined it at the time the linkage unit that uses it was linked. One possible drawback of this approach, if I remember correctly, is that it might not be implementable under Windows. Need input from someone with more current knowledge of the Windows application loader.

5. I originally used "global linkage" throughout this paper, but didn't like it, because "global" has significantly different connotations for most programmers. I also found that "exported", which I have been using, is awkward because its implied direction makes phrases like "exported object and function references" confusing.

6. UNIX and Windows both permit multiple definitions, but with rather different semantics; this rule restricts portable code to common ground.

7. The syntax in the examples assumes the first model.

8. This implies that linkage units can be loaded on call. That may be too hard to specify in conjunction with construction and destruction of static objects, so it may be better to require loading at application startup.