C++ can be terrible when crossing library and build system boundaries. To provide yet another case in point, let me document for posterity a problem I recently ran into when bundling WebRTC
as a third party library in another project - I hope it will save time for someone in the future. So, having sorted out linker problems due to compiling against different C++ standard libraries (libstdc++ vs libc++ - which perhaps deserves discussion on its own), duplicated symbols between BoringSSL and OpenSSL (Ok, this was not a C++ problem) and similar nuisances, I finally had just a couple of linker errors, albeit somewhat cryptic:
undefined reference to `non-virtual thunk to buzz::IqTask::HandleStanza(buzz::XmlElement const*)'
To make it a bit less cryptic: in case of classes with multiple inheritance, vtbl
(table of virtual functions) may not contain pointers to the virtual functions directly, because depending on which pointer the method is called from, an offset may need to be added or subtracted from this pointer before calling that method. This is why virtual table in subclasses points to those short "thunk" functions that usually do just add/sub and jmp - see here and ultimately here for details0.
So, WebRTC
's libjingle
has a header file which defines class IqTask
(a descendant of XmppTask
and a couple other classes, therefore multiple inheritance is involved), and that class has a virtual method HandleStanza()
. Both our code and WebRTC
code include that header, so both need to define a virtual table for that class. Apparently, I missed something and compiler stripped out that thunk from the library or did not generate it altogether?
However, quick check with nm and c++filt did not reveal anything wrong - at first glance, the thunk, referenced in our object file, is present in the library and should link right in:
nm -po *.o | c++filt | grep IqTask::HandleStanza
Module.XMPP.cpp.o: U non-virtual thunk to buzz::IqTask::HandleStanza(buzz::XmlElement const*)
librtc_xmpp.a:rtc_xmpp.iqtask.o:0000000000000360
T non-virtual thunk to buzz::IqTask::HandleStanza(buzz::XmlElement const*)
Baffled, I spent some time reading up on how to better debug the linker, before an idea struck me - c++filt may be masking the problem, I need to see the symbols as they are! And indeed - thunk's mangled names were different between the object files (produced by different build processes, mind you):
Module.XMPP.cpp.o: U _ZThn136_N4buzz6IqTask12HandleStanzaEPKNS_10XmlElementE
librtc_xmpp.a:rtc_xmpp.iqtask.o:0000000000000360:
T _ZThn160_N4buzz6IqTask12HandleStanzaEPKNS_10XmlElementE
I checked the source of __cxa_demangle() for the meaning of those numbers, but did not find any clue how to narrow down the cause of the offset discrepancy. Figuring out that this might have been due to mismatch of class layout or number of methods, which should boil down to differences in compiler options or defined symbols, I proceeded to comparing those.
I'll spare you the process of adding/removing compiler flags in both the build systems (for our code and for the library, which uses gyp), and fast-forward to the end: the difference was caused by _GLIBCXX_DEBUG
defined by WebRTC
in Debug configuration "just in case". Having unset that (by hacking the .gyp file), I was able to produce the library files with the compatible class layout.
This is not the only problem experienced with the library due to it exposing a C++ API, but that's enough to make a point. C++ is a great language for standalone applications, but for anything that needs binary stability, be it run-time or compile-time (i.e., libraries, shared objects), it is a wrong choice. Some of the problems apply to C as well, but linking to a C library is usually much easier, as well as taking control of its memory allocation (which is another feature that WebRTC regrettably lacks).
I'm beating a dead horse here, and the point of C++ being unsuitable for interfacing with other software has been long made. Still, there are libraries with C++ interfaces out there, that, at least in non-trivial projects, cause more pain than gain. Hence, I kindly ask all current and future library authors: no matter what language you use internally, please, provide a C API to your library - like, literally, extern "C" {...}. Thank you in advance.
0. If you decide to compare this with nullptr, more trickery will be needed on compiler side because the pointer may no longer be arithmetically 0 due to these adjustments.↩