Introduction

This Mini‑HOWTO offers advice on installation, and explains the conventions which have been adopted by MinGW, (and also by Cygwin), to identify disparate versions of similarly named shared libraries, (DLLs), and so mitigate the “DLL Hell” which may arise, as a result of software dependencies on potentially incompatible versions of such DLLs.

The content herein is derived from an original web document, originally posted by Soren Andersen, (a.k.a. Perlspinr), and now accessible only via this WayBack Machine archive; this, in turn, was inspired by a no‑longer‑accessible posting, by Charles (Chuck) Wilson, on a mailing‑list discussing Portable Network Graphics (PNG) implementation; the original content has been generalized, to eliminate PNG‑specific references, and expanded upon, in a MinGW‑specific context.

Definitions

PE
The Microsoft Portable Executable  file format; an adaptation of the common object file format, it is sometimes identified as PE‑COFF.
ELF
Executable and Linkable Format ; this is an alternative executable, and shared library file format, used by GNU/Linux, and several Unix systems; it is not used by MS‑Windows.
DLL
A dynamic link library ; this is the terminology favoured by Microsoft, when referring to shared libraries.
Dynamic Linker
An operating system facility, responsible for loading executable program files, and shared libraries, and resolving (linking) symbolic references across executable file and shared library boundaries, to construct a run‑time process image.
Entry Point
This is any one of the publicly visible variable names, or function names, which is exported by a specific DLL.
Interface
This represents the aggregate of all entry points, which are exported by the DLL.
API
This represents the application programming interface ; it is, effectively, the interface, characterized by the entry point names, data types, and function prototypes.
ABI
This is the application binary interface ; it represents, and is characterized by, the machine‑level implementation of the interface.

How the MS‑Windows Dynamic Linker Resolves Shared Library References

When a PE  file, (typically, but not restricted to, an executable  ‘*.exe ’, or a shared library  ‘*.dll ’ file), is created, the build‑time  linker, (e.g. the MinGW linker ... not  the dynamic linker ), embeds references, within the PE  file itself, to any shared libraries on which it depends. Each such reference takes the form of just a DLL file name, without  any directory path name qualification, and there is no direct analogue for the ELFrpath ’ feature.

When the Windows dynamic linker  creates a new process image, it first loads the PE  executable program file into memory. It then attempts to load each shared library, named as a DLL reference within the PE  file, (and iteratively, any further named DLL references within the loaded DLLs themselves), mapping each one into the process address space, and resolving symbol references across DLL boundaries; only after all named DLLs have been loaded, and all symbol references successfully resolved, will execution of the process commence.

To locate each DLL, named in PE  file references, the MS‑Windows dynamic linker will search in each of the following directories, in turn; (this search will continue, through the directory sequence, only as far as is necessary to locate the first  DLL file, with a name which matches the reference):

  1. The directory from which the executable file, itself, was loaded.
  2. The current working directory, at process start‑up time.
  3. The Windows system directory; the path name for this directory may be identified, by calling the GetSystemDirectory() function.
  4. The Windows directory; the path name for this directory may be identified, by calling the GetWindowsDirectory() function.
  5. The sequence of directories, taken in turn, listed in the PATH environment variable; (note that this is the path searched for executables themselves; the LIBPATH environment variable is not considered, and LD_LIBRARY_PATH — a standard environment variable which is commonly associated with ELF  shared libraries — has no defined purpose in the MS‑Windows environment).

Note that each uniquely named DLL file, irrespective of the directory path from whence it is loaded, will be mapped into the process address space only once. Furthermore, if the instance of each named DLL, which is located first in the above directory search sequence, fails to resolve all entry points  which it is expected to provide, process execution will fail; the search will not  be resumed, even if a similarly named DLL file, from a later directory in the search sequence, may be an alternative version, from which the missing entry points  could have been resolved.

It is important to ensure that, if an application requires a particular version of any DLL, that the correct DLL is installed in a location whence it will be identified early in the preceding search sequence. In a conventional MinGW installation, (typically in C:\MinGW), this is normally achieved by installation of both  the *.exe files, and  their associated DLLs, in the common C:\MinGW\bin directory, so that DLL identification is completed in accordance with rule (1), above.

Alternatively, for any application whose *.exe files are not  installed in the C:\MinGW\bin directory, (since C:\MinGW\bin will typically be listed within the user’s PATH environment variable), MinGW DLLs may be identified in accordance with rule (5). When applications depend on this DLL identification stratagem, it is strongly  recommended that all  MinGW DLLs be kept fully up to date, to ensure that compatibility is maintained, as explained below, for all  dependent applications, regardless of age.

How MinGW Shared Library Version Numbers are Assigned

The single value, as assigned as a MinGW shared library version number, is derived from an effective GNU libtool current:revision:age triplet, which itself, is managed in accordance with the convention described in libtool‑versioning section of the GNU libtool manual:

[...] libtool library versions are described by three integers:

current
The most recent interface [ABI version] number that this library implements.
revision
The implementation [revision] number of the current  interface.
age
The difference between [the ABI version numbers of] the newest and oldest interfaces that this library implements. In other words, the library implements all the interface numbers in the range from number current-age  to current.

These current:revision:age attributes are assigned, by the maintainer of the library, as specified in the immediately following section of the GNU libtool manual:

Here are a set of rules to help you update your library version information:

  1. Start with version information of ‘0:0:0’ for each libtool library.
  2. Update the version information only immediately before a public release of your software. More frequent updates are unnecessary, and only guarantee that the current interface number gets larger faster.
  3. If the library source code has changed at all, since the [version information was last updated], then increment revision(i.e. ‘current:revision:age ’ becomes ‘current:revision+1:age ’ ).
  4. If any interfaces have been added, removed, or changed since the last update, increment current, and set revision  to 0.
  5. If any interfaces [i.e. entry points ] have been added since the last public release, then increment age.
  6. If any interfaces [i.e. entry points ] have been removed, or [the data type or function prototype of any entry point has been] changed since the last public release, then set age  to 0.

Never try to set the interface numbers so that they correspond to the release number of your package. This is an abuse that only fosters misunderstanding of the purpose of library versions. [...]

The following explanation may help [you] to understand the above rules a bit better: consider that there are three possible kinds of reactions from users of your library to changes in a shared library:

  1. Programs using the previous version may use the new version as drop-in replacement, and programs using the new version can also work with the previous one. In other words, no recompiling nor relinking is needed. In this case, bump revision  only, don’t touch current  [or] age.
  2. Programs using the previous version may use the new version as drop-in replacement, but programs using the new version may use APIs [which are] not present in the previous one. In other words, a program linking against the new version may fail with “unresolved symbols” if [deployed with] the old version at runtime: set revision  to 0, bump [i.e. increment both] current  and age.
  3. Programs may need to be changed, recompiled, and relinked in order to use the new version. Bump current, set [both] revision  and age  to 0.

In the above description, programs using the library in question may also be replaced by other libraries using it.

To derive the single‑valued MinGW shared library version number, from the GNU libtool compatible current:revision:age  triplet, we adopt the convention originally suggested by Gary Vaughan, in a posting to the Cygwin mailing‑list, to the effect that the effective shared library version should be set equal to the version number of the oldest ABI  version supported by the shared library; this is equivalent to the result of the calculation current-age, taking the individual values of current, and age  from the libtool triplet. (Note that this does not require  use of libtool for maintanence of the shared library; it is sufficient to adopt the libtool numbering convention, and to calculate current-age  manually).

Evolution of MinGW Shared Library Versions

To further illustrate the evolution of MinGW shared library versions, let us consider the development life‑cycle of a hypothetical library, libfoo.dll, for which the most recent release corresponds to a GNU libtool current:revision:age  triplet of 5:4:3; this indicates that the current ABI  version of this hypothetical library is 5, and that this version is fully backwardly compatible  with each of the three  preceding releases, with ABI  version numbers 4, 3, and 2; thus, the MinGW library version, computed as current-age, will be 2, yielding a versioned library name of libfoo-2.dll; it may be observed that this version‑indicating name corresponds to the oldest ABI  version which is fully compatible with the current ABI  version 5 release.

There are many evolutionary paths, which libfoo‑2.dll may have followed, to become the equivalent of a libtool‑managed 5:4:3 release; the following example represents just one such possible path:

  1. Regardless of the path followed, the starting point is always equivalent to libtool release 0:0:0, yielding a computed current-age  value of 0-0, (or simply 0); thus, the MinGW designation for the initial release will be libfoo‑0.dll.
  2. The library goes through several release cycles, without any change in, addition to, or removal from its public interface ; the libtool revision  is incremented, at each release, through 0:1:0, 0:2:0, ..., but, since neither current, nor age  is changed, the MinGW release designation remains as libfoo‑0.dll.

    This is correct, because the interface  remains unchanged from the initial  release; programs linked against the initial release may continue to use this libfoo‑0.dll, as a drop‑in replacement for the original, while benefitting from any bug‑fixes which may have been applied in the newer releases.

  3. Following the release of libfoo‑0.dll, corresponding to (say) libtool release 0:4:0, a new function entry point  is added, without  changing the established interface in any way. This introduces a forwardly incompatible  change in the API,  (because any new application which depends on the new entry point  will be incompatible with any earlier release of libfoo‑0.dll); however, the API  remains backwardly compatible  with previous releases, (because none  of the previously existing entry points  exhibit any change in behaviour).

    To reflect this change in compatibility, the libtool current  version number is incremented, while resetting the revision  to 0, (to account for the addition of the new entry point ); at the same time, age  is incremented in lock‑step, (because backward  compatibility, with the preceding release, is preserved). Consequently, the libtool release identification becomes 1:0:1, since the result of computing current-age, (which now becomes 1-1), remains equal to 0, and thus, the MinGW library designation remains as libfoo‑0.dll. Once again, this is correct; in spite of the change in libtool current  release number, the oldest  value of current  release, with which this release remains backwardly  compatible, is still 0.

  4. After the release of libfoo‑0.dll, at libtool release point 1:0:1, one of the publicly visible entry point  functions is deemed to have become obsolete, and is removed. This represents another change to the public interface, so once again the libtool current  release number must be incremented, and the revision  reset; however, this is not  a backwardly compatible  change, so, for this release, age  is reset to 0, rather than being incremented. The effect of this is that the libtool release number advances to 2:0:0, and the MinGW release number, computed as current-age, advances to 2, resulting in a new MinGW library designation of libfoo‑2.dll.

    Note that this is, once again, correct: the libtool current  release number has advanced, to 2; this release is no longer backwardly  compatible with any other release, older than itself, and the MinGW release number has also advanced accordingly; this libfoo‑2.dll is not  suitable for use as a drop‑in replacement for libfoo‑0.dll, and the DLL file name has been changed, to prevent any such misuse.

  5. The next few release cycles, following the preceding release of libfoo‑2.dll, may proceed as described in (2), above, thus requiring only the libtool revision  to be incremented, without  affecting the MinGW release number, in any way. These may be interspersed with three further cycles, similar to that described in (3), above, in which any number of new entry points  are added, but none  are removed or modified; after the third such type (3) release, the libtool release number will have advanced through 3:0:1, 4:0:2, and finally 5:0:3. If this is then followed by by four further type (2) releases, there will be four further increments in the libtool revision, ultimately advancing the libtool release number to 5:4:3; at this stage in the release cycle sequence, the current-age  computation will continue to yield a MinGW release number of 2, and the MinGW library will continue to be named libfoo‑2.dll.

It may be observed that, following the preceding sequence of release cycles, whereas the libtool release number is able to convey the information that the current library version implements revision 4 of the implementation of version 5 of the interface, and that this implementation is fully backwardly compatible  with version 2 of this interface, the MinGW release number cannot adequately convey any more than the last of these pieces of information. Although this limitation may appear to be problematic, in practice it isn’t, provided  the installed version of libfoo‑2.dll is its most recently released distribution; unlike ELF dynamic linkers, the PE dynamic linker  simply isn’t smart enough to select a DLL on the basis of a range  of supported interface  versions, so the best we can hope for is that the selected library, as named for its oldest  supported version, covers the required range; the most effective assurance that we can have for this is that the selected library is the most recently released distribution, with the specified name.

Conclusion

“DLL Hell” arises when two identically named shared library files provide different (incompatible) APIs  ... perhaps even incompatible versions of fundamentally the same API ; a common cause is that installation of some third‑party software product has overwritten an installed DLL file with an obsolete version.

Sometimes, a particular software product requires a particular version of a specific DLL, with which the most recent version of that DLL is not backwardly compatible,  (because the developer of that DLL may not have exercised good version control discipline). To avoid this kind of issue, MinGW has adopted a DLL version management discipline, conforming to the following conventions:

Adoption of these conventions ensures that, if the most recent release of each, and every required MinGW DLL is installed, at an appropriate location within the DLL directory search path, then, on account of the promise of backwards compatibility,  applications which are dependent on any releases of these DLLs will continue to operate as intended. Additionally, it allows releases of mutually incompatible, similarly named (but for the version identifier) DLLs to co‑exist, within the DLL search path, thus ensuring that applications which may depend on older, incompatible DLL versions, may continue to operate correctly.

Unfortunately, the conventions alone cannot  prevent any user, or third‑party package installer, from replacing the most recent MinGW release of any DLL with an older release, (or with an incompatible third‑party DLL with the same name). Since forward compatibility  is never  promised, for an older MinGW DLL release used in conjunction with an application which may be dependent on a more recent release, (nor is there any  expectation of any form of compatibility  from any  third‑party DLL), it becomes incumbent upon the user, to ensure that only  the most recent releases of MinGW DLLs are (and remain) installed.