5 ms·
DLL hell is mostly an issue of incompatible CRTs (aka libc in Unix parlance) on Windows. Historically (until circa 2015) Windows programs (particularly those co
by wahern 8y ago
DLL hell is mostly an issue of incompatible CRTs (aka libc in Unix parlance) on Windows. Historically (until circa 2015) Windows programs (particularly those compiled with Visual Studio) often loaded a multiplicity of incompatible CRTs. It was often the case that malloc'd memory from one DLL could not be free'd from another DLL because each linked to a different CRT. Compounding matters, applications would often globally install popular DLLs which, even if compiled from the exact same source code, would break apps if linked against a different CRT version at build time. Which would invariably be the case as Visual Studio by default links in its own version-specific CRT rather than the system CRT, the Linux equivalent of GCC shipping and statically linking in a new version of musl libc, making mixing-and-matching of shared libraries compiled in different environments a risky endeavor indeed.
On Unix there's almost always a single, system-wide CRT. Moreover, commercial environments like Solaris as well as Linux/glibc have maintained strong backwards compatibility (mostly a matter of ABI stability) so that compiled programs continue to work correctly with future libc versions. Believe it or not, third-party dependencies aside, compiling a C program on Red Hat and successfully (and correctly) running on Debian is the norm, not the exception, assuming you never on execute on a system with an older version of glibc than during compilation.[1] (Even though musl libc doesn't do symbol versioning, I assume the story is similar as it rigorously sticks to exposing only POSIX interfaces and opaque structures as much as it can.)
Therefore, DLL hell is a much less pronounced issue in Linux land. A typical example involves the loading of incompatible versions of third-party libraries. But most widely used third-party libraries like libz or libxml2 have maintained strong backward compatibility. The only culprit I've repeatedly encountered in this regard is OpenSSL, which until OpenSSL 1.1 never committed to a stable ABI, largely because it relied so heavily on exposed, preprocessor generated structures as opposed to idiomatic opaque structure pointers. But IME this was more an issue on macOS as the norm in Linux is to simply use the system OpenSSL version. (Who here bothers to download, build, and install OpenSSL in their containers rather than using the system version of their Debian- or RedHat-derived container base image? It's common enough at large companies but hardly so common that it was a substantial factor in the adoption of containers.)
The ELF toolchain on modern Linux systems is actually sufficiently advanced that you could theoretically support the loading of multiple versions of a library like OpenSSL (as long as you didn't pass objects between them), such as the SONAME linker tag and the far more powerful but little known ELF symbol versioning. See, e.g., https://www.berrange.com/posts/2011/01/13/versioning-in-the-libvirt-library/ https://www.berrange.com/posts/2011/01/13/versioning-in-the-...
Alas, SONAME is used inconsistently and ELF symbol versioning almost not at all--only symbol versioning users of note AFAIK are glibc and libvirt. It's at this point that executing in containers becomes the path of least resistance.
It's true that installing scripting language packages or building complex C++ programs which have evolved nightmarish dependency trees as impressive as Node.js/npm is a huge motivator for containerization, but that's not the kind of thing that is meant by DLL hell. And historically this issue was sufficiently addressed by packaging systems. It's no coincidence Ubuntu is the most popular container base image--the Ubuntu package repository is multiple times the size of Red Hat's repository, and much more up-to-date. (It's larger even than CentOS' or Fedora's repositories, even when including third-party repositories like EPEL.) In other words, even in the era of containers people still largely rely on the older packaging infrastructure.
[1] Maintaining this "never older than" invariant is an important caveat, but IME I've run into more issues with kernel versions (e.g. unsupported syscalls) than with libc versions. Containers don't solve the kernel version issue.
- AnIdiotOnTheNet 8y agoYou say all these words, and yet distributing an application on Windows or MacOS is trivial, but doing the same for Linux is widely regarded as a giant pain in the ass for anyone who isn't just scattering their source into the wind for some maintainer to deal with (often multiple maintainers).
- wahern 8y agoAs someone who has principally programmed for Linux and Unix, the reverse is true for me. Building and packaging for Linux and traditional Unix environments feels very straight forward and transparent to me, while building and packaging for Windows and (to a lesser extent) macOS seems like a dark art. And I think that's largely because "properly" built and packaged applications (particularly GUI applications) for those systems should use Visual Studio or XCode, which require learning and applying proprietary and restrictive (to me) build and link processes. And those processes are geared toward building and packaging dependencies together, whether linking dynamically or statically[1], which in the Unix universe was an anti-pattern to be avoided as much as possible. More to the point, the phrase DLL Hell was literally coined by and for Windows developers to describe Windows-specific headaches. This much is a fact. A minority of people in the Unix universe use the term DLL, certainly in the 1990s when DLL Hell became a meme; the term shared library was and remains more common. The usage of DLL Hell in the general sense of exclaiming linking and packaging in a particular environment to be too complex and brittle is quite recent and uncommon, though increasing. I did say alot of words, though :) It was late.... [1] Notably the Debian/Ubuntu universe rather strictly requires the packaging of statically linkable libraries. With rare exception the foo-dev package should always install a useable libfoo.a and, if supported, foo.a for module-based frameworks that permit static embedding, even if the upstream project only builds a shared library. This makes statically linking trivial without having to bundle your dependencies into your build; and makes it easier to stick to a policy of using system-installed dependencies, minimizing the risk of transitive dependency conflicts, even when statically linking, as the maintainers do the hard work of ensuring version compatibility holistically. Red Hat has a similar policy but in IME more poorly executed. On multiple occasions I've found RPM-packaged static libraries (including those built by Red Hat, such as liblua.a) to have been built with the wrong build flags, causing unnecessary namespace and linking headaches, sometimes forcing me to bundle the dependency directly into my build or creating a bespoke RPM package. More generally I've found the RPM universe to be much more inconsistent and problematic, and instead of fixing these technical and functional issues Red Hat expends most of their effort attempting to bypass, rather than complement, the RPM ecosystem. By contrast Debian and Ubuntu package maintainers do a better job of fixing broken upstream builds, which makes life easier for everybody. If I had the choice, I'd stick to supporting only Debian-based operating systems precisely for this reason.