Search code examples
c#c++dllcom

If DLL's in C++ don't support binary encapsulation, when is it ever right to use them?


So I'm reading up on COM's and DLL's, and it seems as though, that if I'm ever to update the implementation of the DLL (if the needed memory exceeds the old need), I need to recompile the program using the DLL with the updated DLL. So I'm just wondering, when is it ever right to use DLL's in favor of COM? It seems nearly impossible to get all the programs recompiled, just because a dependency they have updated.

Also, just a quick side-question: Why is binary encapsulation not a problem in .NET? How come I can change a DLL just find in .NET without having to recompile my old project, without using pimpl/opaque pointers and whatnot?


Solution

  • Binary compatibility requires caller and callee to agree on a common ABI (application binary interface).

    On both Windows and Linux, a standard ABI for C calls is specified by the OS, which allows calls into the C library from any other language. This also makes C++ useful for DLL development, as long as C++ is used only inside the DLL and the outer surface uses extern "C" and C-compatible types such as pointers and primitives and plain-old-data composites of C-compatible types.

    On both Linux and Windows, the C++ ABI is associated with the C++ runtime library. On Linux, people are used to fooling themselves into thinking that C++ shared objects also had an OS-provided ABI, because there was only one widely used C++ library -- g++'s libstdc++, and because that library went to great lengths to preserve the same ABI. So pre-C++11 libstdc++ ABI was a defacto standard, and no one ran into trouble from mixing ABIs. Now C++11 forced libstdc++ to break binary compatibility, and clang's libc++ has grown in popularity, so Linux developers are facing the problems Windows developers have been familiar with for a long time.

    On Windows, no C++ standard library ever became dominant in quite the same way, partially because every version of the OS vendor tools (Microsoft Visual C++) introduced incompatibility. Yes, some things stayed largely the same (name mangling) but others changed radically (allocators got low fragmentation heap support, containers got debug iterators, etc).

    The ironic thing is that ABI standardization on Windows is now better than on Linux1, because the Windows ABI specifies one C++ feature -- vtable layout -- to support COM.

    The bottom line for native C++ is that you can use it freely inside a DLL, but for decoupling, the public surface of the DLL needs to use only features specified in the OS ABI -- extern "C" names, C-compatible types, and (Windows only) abstract base classes consisting only of virtual functions (no static members, no non-static data, all non-static function are virtual, and constructors are protected).

    But if you can have coupling, you get more features. If every component in an application agrees to the .NET ABI, then all its features become available. And .NET's ABI was developed with backward and forward compatibility in mind, learning lessons from incompatibilities in C++, and from previous attempts such as Java.

    Because the .NET ABI is so stable, coupling to it is a viable decision, just like so many Linux developers coupled to the pre-C++11 stdlibc++ ABI. But you run the risk of losing all your reusable libraries if you ever break compatibility, which is why coupling to any particular Windows C++ library was considered a bad idea -- upgrading your compiler required you to have new versions of every single library. When having even a single component stuck on the old version prevents adopting a new compiler, you lose new development on other components which have switched. Which is why C++ ABI coupling on Windows is widely considered a very bad idea. And why library authors rarely distribute Linux code in binary form -- they leave it up to each distribution to pick an ABI, consistently compile using it, and serve up binary packages compatible with it.

    And of course, this only addresses changes that come from outside your source code (layout changes to library types, parameter passing order, etc). If your source code changes, for example by adding a new function parameter or changing the data members in a class type, then you will have incompatibility no matter how good (well-specified/stable) the ABI is. .NET escapes some of these effects by the two-phase compile system -- the whole world does get recompiled when dependencies change, because JIT compilation is done at runtime. But even in .NET, some changes like adding new function overloads, will need a source recompile before they take effect.


    Actually I'm not sure, vtable-layout might also be specified at the OS level in Linux. The important thing is that Linux C++ binary compatiblity is no longer universal, which comes as a huge surprise to a lot of developers who were actually relying on a defacto-standard compiler-provided ABI, while assuming the OS ABI guaranteed universal compatibility forever. So Windows developers, having already learned to manage lack of binary compatibility, are in a better position.