0

I have the following MWE:

// classes.hpp
#pragma once

struct Base {
    virtual ~Base() = default;
    virtual void tell() = 0;
};

struct Derived : Base {
    Derived() = default;
    ~Derived() = default;
    void tell() override;
};

then,

// classes.cpp
#include <iostream>
#include "classes.hpp"

extern "C" {
Derived* instantiate()
{
    return new Derived{};
}

void deinstantiate(Derived* ptr)
{
    delete ptr;
}
}

void Derived::tell()
{
    std::cout << "Hello!" << std::endl;
}

which I compile on a linux machine as a shared library g++ -fPIC -shared -o classes.so classes.cpp and the main program:

// main.cpp
#include <iostream>
#include <dlfcn.h>

#include "classes.hpp"


int main()
{
    using inst_t = Derived*(*)(void);
    using deinst_t = void(*)(Derived*);

    void* handle = dlopen("./classes.so", RTLD_NOW);
    inst_t inst = reinterpret_cast<inst_t>(dlsym(handle, "instantiate"));
    deinst_t deinst = reinterpret_cast<deinst_t>(dlsym(handle, "deinstantiate"));
    // some tests here...

    Derived* instance = inst();
    instance->tell();
    deinst(instance);
    dlclose(handle);

    return EXIT_SUCCESS;
}

which I compile with g++ -ldl -o main main.cpp

It works well. However If I remove the inheritance of Derived from Base, i.e. if I substitute above calsses.hpp file with

// new classes.hpp
#pragma once

struct Derived {
    Derived() = default;
    ~Derived() = default;
    void tell();
};

I got a link error

/usr/bin/ld: /tmp/ccLuFAZr.o: in function `main':
main.cpp:(.text+0x66): undefined reference to `Derived::tell()'
collect2: error: ld returned 1 exit status

which I is sensible since it cannot find the symbol from classes.cpp. However it is not completely clear to me how subclassing Derived from Base can solve this problem since Base is not used at all. (I think it should be a matter o vtable creation). So

  1. How is it possible?
  2. Is there any other way to solve the problem without using (dummy) inheritance?

EDIT

Of course the cause of a linking error can be found in a question whose answers list all the possible cause of linking errors. However, in my opinion this question is by far more generic than mine which deals with a particular situation (the use of dynamic shared library, ecc).

EDIT 2

Non-question: Why, without inheritance, do I get link error? (I know)

Question: Why using inheritance do I not get link error?

MaPo
  • 613
  • 4
  • 9

2 Answers2

1

void Derived::tell() is defined in libclasses.so.

With #include "classes.hpp", the class is defined in main.cc, instance->tell(); is an attempt to call void Derived::tell() defined in main, and the member function is not defined there for sure.

You might load the symbol void Derived::tell() from libclasses.so and call it like below:

// Derived::tell() is the _ZN7Derived4tellEv mangled name.
void (Derived::*tell)() = reinterpret_cast<void(Derived::*)()>(dlsym(handle, "_ZN7Derived4tellEv"));
(instance->*tell)();

Unfortunately the cast a pointer to a member function pointer is not allowed. You should wrap a member call into a regular function in libclasses.so:

extern "C" void call_tell(Derived* d) {
  d->tell();
}
auto* tell = reinterpret_cast<void(*)(Derived* d)>(dlsym(handle, "call_tell"));
tell(instance);
273K
  • 29,503
  • 10
  • 41
  • 64
  • Thank you for the answer. What I do not understand is why with a vtable it works. – MaPo Jun 07 '23 at 07:09
  • But how can this vtable be looked for in `main` program which is another compilation unit? – MaPo Jun 07 '23 at 07:21
  • 1
    If you call `new Derived{}` in main you get the error again. `inst()` doesn't look for vtbl in main. – 273K Jun 07 '23 at 07:22
  • This I understand. What I still do not understand is how it is possible, since the vtable is defined in `classes.so`, that it can be looked for in `main` with the instruction `instance->tell()`. – MaPo Jun 07 '23 at 07:28
  • Do you mean that the `main` is aware of the vtable since vtable are build using declaration and not definition? – MaPo Jun 07 '23 at 07:33
  • 1
    Yes, main is aware of `vtbl` structure. – 273K Jun 07 '23 at 07:37
  • But once it is aware, vtable, i think, should contain a pointer to `Derived::tell()` which is defined in `classes.so`. How can the vtable known by `main` program know the location of `classes.so`? – MaPo Jun 07 '23 at 07:57
  • 1
    `main` has incomplete vtbl, it's just a pointer, `inst()` SETS the pointer to the complete vtbl from .so. main DOES NOT SET vtbl. Please stop going in circle. – 273K Jun 07 '23 at 08:02
0

You need to put the "void tell();" from Derived as "virtual void tell();". The derived should be:

struct Derived {
  Derived() = default;
  ~Derived() = default;
  virtual void tell();

};

hleme
  • 90
  • 3
  • This works, but why? Why it seems mandatory to create a virtual table? – MaPo Jun 06 '23 at 20:56
  • Yes, but where the information of the vtbl are stored? How can the vtbl locate the symbol? – MaPo Jun 06 '23 at 21:47
  • 1
    Answer could do with a bit more meat. WHY does making `tell` virtual solve the problem? – user4581301 Jun 06 '23 at 22:06
  • 1
    The `Derived` class has its only `tell` method defined in the `classes.so` library. However, this class was declared in `main` without any method defined. When you call `instance->tell()` in main, it doesn't have a defined method to execute. If you declare this virtual method, the program can pass execution to `tell()` in `classes.o`. – hleme Jun 06 '23 at 22:35
  • But there are two separate compilation unit. Is it guaranteed that the two vtbl correspond? – MaPo Jun 06 '23 at 22:42
  • I checked the `classes.so` symbol table, the `tell()` method is there with or without the `virtual` modifier. It is only necessary so that the `main` program (which does not have `tell()` defined) can pass on to the external library `classes.so` to execute this method. To see the `classes.o` symbols `objdump -T classes.so`. The answer is yes, they will match. – hleme Jun 06 '23 at 23:08