Search code examples
c++c++11c++14dynamic-castdynamic-library

Downcasting types returned from dynamic library


I'm writing a dynamic library which returns pointers to base class but I want to downcast them to derived class. Example:

//Library code:
class A
{
public:
    A(void) = default;
    virtual ~A(void) = default;
    virtual void Foo(void)
    {
        std::cout << "A::Foo\n";
    };
};

class B : public A
{
public:
    B(void) = default;
    void Foo(void) override
    {
        std::cout << "B::Foo\n";
    }
    virtual void Bar(void)
    {
        std::cout << "B::Bar\n";
    }
};

class Factory
{
public:
    Factory(void) = default;
    virtual std::shared_ptr<A> CreateB(void)
    {
        return std::shared_ptr<A>{new B{}};
    }
};

extern "C" std::shared_ptr<Factory> CreateFactory(void)
{
    return std::make_shared<Factory>();
}


//Application code:
int main(int argc, char* argv[])
try
{
    auto handle = dlopen("./Dynamic.so", RTLD_LAZY);
    if (handle == nullptr)
    {
        throw std::runtime_error{dlerror()};
    }
    auto factoryaddress = 
        reinterpret_cast<std::shared_ptr<Factory>(*)(void)>(
        dlsym(handle, "CreateFactory"));
    if (factoryaddress == nullptr)
    {
        dlclose(handle);
        throw std::runtime_error{dlerror()};
    }
    auto factory = factoryaddress();
    auto a = factory->CreateB();
    a->Foo();
    auto b = std::dynamic_pointer_cast<B>(a);
    b->Bar();
    std::cin.get();
}

I get

/usr/include/c++/5/bits/shared_ptr.h:458: undefined reference to `typeinfo for B'
/usr/include/c++/5/bits/shared_ptr.h:458: undefined reference to `typeinfo for A'

Solution

  • I bet you show different code than you actually try to compile. In order to have a build time linking problem, you have to

    1. Compile with RTTI support, which you do because you use dynamic_cast, and
    2. Have a non-inline virtual function in the class, whose RTTI information you use.

    In your code everything is inline, but if you take, say the ~A() and put it into a separate source file (not in common header), you'll get this error.

    The solution is simple, yet somewhat ugly. All exported classes must have all virtual function inline. You can have a private non-inline non-virtual functions with actual implementations and just call one from another.

    The reason behind all this is that RTTI information (typeinfo and some other symbols) are emitted if translation unit defines first non-inline virtual function of the class. In you case, when you compile application, compiler doesn't see any virtual functions definitions and assumes that typeinfos are in some other object file. Which is not true, thus linker fails.

    When all virtual functions are inline, RTTI stuff is emitted as weak (a.k.a. "comdat" section) in every object file that refers to the class. So you may share the class between library and application, both will have identical weak RTTI symbols. When application loads the library, second duplicate set of RTTI symbols is discarded and library code is linked to symbols already present in application code. This is common weak symbols mechanics.