2

I have an interface, and I have an implementation of that interface in a shared library, and who knows what it was compiled with.

dynamic_cast works only on polymorphic types. So I can assume it does not use RTTI, so I do disable it, and it compiles.

And since there's no standardized way of doing polymorphism, the layout of an object of a polymorphic type is unknown, so is the layout of its vtable.

Thus the question arises: can I use an object of a polymorphic type from a shared library that was compiled with a compiler different from the one I use? And, more importantly, why?

Hrisip
  • 900
  • 4
  • 13
  • 1
    You answered the question yourself. There is no standardized ABIs for virtual calls, so absent any implicit guarantees, you can't use it. – SergeyA Sep 18 '19 at 19:59
  • @SergeyA, my fears are justified... The whole world of mine begins to collapse – Hrisip Sep 18 '19 at 20:06
  • Well, don't be so grim. If you are on Linux, I am pretty certain all 3 big compilers (icc, CLang, gcc) will use same mechanisms for virtual dispatch. – SergeyA Sep 18 '19 at 20:13
  • Same on Mac, all the compilers use the same ABI. On Windows gcc wont use the visual studio ABI but clang can if you use the right switches – Alan Birtles Sep 18 '19 at 20:14
  • I know, there's a great chance the ABIs will match, but still, it's not guaranteed. Even the same compiler can change the ABI(though it's extremely unlikely in the front of object and virtual tables layout). BTY, I'm on Windows, deep in legacy. – Hrisip Sep 18 '19 at 20:22
  • There is no compatibility guaranteed even if everything is compiled with the same compiler. In order to use any kind of objects from dll both binaries must be compiled with compatible settings (which typically implies not only the same version of the compiler but also relevant arguments being the same). `dynamic_cast` does use RTTI when casting from parent class to child class. If you are looking for a standardized way of doing polymorphism you should rely on pure C api like COM does. With C compilers are less likely to break things. – user7860670 Sep 18 '19 at 20:25
  • @VTT if `dynamic_cast` does use RTTI,how do you explain the need for the type to be polymorphic and the absence of errors when I turn off RTTI and use `dynamic_cast`. It can deduce the type from the vtable pointer, why would it bother with some type information(that actually rests in the vtable itself, if I'm not mistaken). And I don't see how the fact that COM uses C makes it any better. You still would write a COM object in C++, which means that your compiler must generate the same code COM expects. And there's nothing a C compiler can break there, since an interface is just a `struct` in C – Hrisip Sep 18 '19 at 20:36
  • It can deduce the type from the runtime information accessible through the same vtable pointer. *"absence of errors when I turn off RTTI and use dynamic_cast"* - mcve or it did not happen. Note that compilers typically issue a warning or an error when `dynamic_cast` is used while RTTI is disabled. C makes it better by prohibiting use of C++ABI dependent types in public interface. COM also defines format of vtable and member function pointers stored in it, as well as COM-style RTTI and `dynamic_cast` through QuoeryInterface which is again does not depend on C++ABI or RTTI. – user7860670 Sep 18 '19 at 20:46
  • COM (CORBA) specifies the ABI for objects and their interactions. Without it, there is just the.platform ABI, specifying structs and free functions. You can write COM in C++; the compiler will then make sure to generate interoperable code. – rustyx Sep 18 '19 at 20:50
  • How do you think `dynamic_cast` is implemented? It's probably just a hidden function added to the vtable. – Mark Ransom Sep 18 '19 at 22:25
  • @VTT, Suppose you have a registered COM object in some .dll written in C++, so this object is just a class that inherits from a class that has only pure virtual functions. You create that object, meaning you get a pointer to an object layout that is unknown, like what if the vtable pointer is in the end of the layout, what if the pointers in the table are rearranged. Which means you get UB when you call a method of such object. The only way to enforce it is to have some special flags in the compiler. I really don't see how it can be guaranteed by the standard. Or I'm not seeing your point... – Hrisip Sep 19 '19 at 05:52
  • If you have a COM object in some dll written in C, C++, C#, Ada or whatever other language this means that layout of interface object (position of vtable pointer that is), structure of vtable and calling convention of functions are all well-defined. So COM interface consumers may use COM objects even if implementation languages are completely different. But it is not in case if you are trying to use some abstract C++ class as an interface to work with random C++ dll. – user7860670 Sep 19 '19 at 09:13

1 Answers1

1

C++ doesn't have a standardized ABI but if your interface follows a couple of rules, you can achieve ABI compatibility between most compilers - especially on Windows. This technique is most famously used in Microsoft's COM (https://en.wikipedia.org/wiki/Component_Object_Model), but I've also seen it in Steinberg's VST Model Architecture (https://steinbergmedia.github.io/vst3_doc/base/index.html) and in the "openvr" library (https://github.com/ValveSoftware/openvr/blob/5aa6c5f0f6520c59c4dce124541ecc62604fd7a5/headers/openvr.h#L1940).

Here are the basic rules for such ABI compatible C++ interfaces:

  1. Only pure virtual methods. This makes multiple inheritance relatively unproblematic. One important caveat is that only the implementation side is allowed to perform a static_cast from one interface to another (to guarantee the right this-pointer adjustment). To solve this problem, every COM interface provides the QueryInterface() method, which is a bit similar to a dynamic_cast.
  2. No virtual destructors. Some compilers generate more than 1 vtable entry (see Why do I have two destructor implementations in my assembly output?). This means you must implement your own mechanism for destructing an object from a base pointer. COM and VST3 use reference counting with addRef() and release() and they have their own type of client-side smart pointers. Alternatively, you can have a simple destroy() method and store your object instance in a regular std::unique_ptr or std::shared_ptr with a custom deleter.
  3. Memory management must not cross interface boundaries, i.e. you must not allocate memory on one side and deallocate on the other side, because each side might use a different runtime. The library has to provide a free function or interface method to deallocate objects. It might also allow users to pass an allocator, so the memory management stays on the client side.
  4. No overloaded virtual methods. Most compilers generally put methods in the vtable in the order of their declaration, but apparently the order of overloaded methods in the vtable varies across compilers.
  5. All method parameters must be primitive types or public classes with a stable object layout (preferable PODs). You must not use any classes from the C++ standard library like std::string because the implementation is not stable.
  6. Once the interface is published, it must never change. You can extend existing interfaces by inheriting from them:
class IFoo { 
public:
    virtual void foo() = 0; 
};

class IFooEx : public IFoo {
public:
    virtual void bar() = 0; 
};

or add a new interface and use multiple inheritance.

That being said, as a library author you should think twice before choosing this technique, especially if you plan to add bindings for other languages. Although the vtables of such C++ interface can be translated to C structs of function pointers, the usual approaches are

a) start with a C API and provide a client-side C++ wrapper

b) use regular C++ interfaces and provide a C layer on top of it.

But with the right abstractions, COM-like C++ interfaces can be quite nice to program with.

  • There is no such thing as "ABI compatible C++ interfaces". One can not hope to be able to all method of some class through a some abstract base class. COM works because it defines ABI. Writing COM-like C++ interfaces does not automatically mean that objects can be used from code compiled with different compiler. – user7860670 Sep 19 '19 at 09:18
  • My very first sentence is: "C++ doesn't have a standardized ABI, but if your interface follows a couple of rules, you can achieve ABI compatibility between most compilers." Maybe I should stress the word "most"? So yes, it's not guaranteed by the standard but in practice, COM-like C++ interfaces are supported by all major compilers, *especially* on Windows. The VST3 SDK has a cross-platform COM-like plugin interface that has been around for over 10 years now... – Spacechild1 Sep 19 '19 at 13:12