динамическое приведение не удается при использовании с dlopen / dlsym


Вступление

Позвольте мне заранее извиниться за длинный вопрос. Она настолько коротка, насколько я могу ее сделать, что, к сожалению, не очень коротка.

Настройка

Я определил два интерфейса, A и B:

class A // An interface
{
public:
  virtual ~A() {}

  virtual void whatever_A()=0;
};

class B // Another interface
{
public:
  virtual ~B() {}

  virtual void whatever_B()=0;
};

Затем у меня есть общая библиотека "testc", которая строит объекты класса C, реализует как A, так и B, а затем передает указатели на их a-интерфейс:

class C: public A, public B
{
public:
  C();
  ~C();

  virtual void whatever_A();
  virtual void whatever_B();
};

A* create()
{
  return new C();
}

Наконец, у меня есть вторая общая библиотека "testd", которая принимает A* в качестве входных данных, и пытается привести его к B*, используя dynamic_cast

void process(A* a)
{
  B* b = dynamic_cast<B*>(a);
  if(b)
    b->whatever_B();
  else
    printf("Failed!n");
}

Наконец, у меня есть основное приложение, передающее A* между библиотеками:

A* a = create();
process(a);

Вопрос

Если я создаю свое основное приложение, связывая его с библиотеками "testc" и "testd", все работает так, как ожидалось. Если, Однако, я изменю основное приложение, чтобы не связываться с 'testc' и 'testd', а вместо этого загрузить их во время выполнения с помощью dlopen/dlsym, тогда dynamic_cast терпит неудачу.

Я не понимаю, почему. Какой-нибудь улики?

Дополнительная информация

  • протестировано с gcc 4.4.1, libc6 2.10.1 (Ubuntu 9.10)
  • пример кода доступно
4 13

4 ответа:

Я нашел ответ на свой вопрос здесь. Как я понимаю, мне нужно сделать объект TypeInfo в testc, имеющиеся в testd библиотеки''. Для этого при использовании dlopen() необходимо сделать две дополнительные вещи:

  • при связывании библиотеки передайте компоновщику параметр -E, чтобы убедиться, что он экспортирует все символы в исполняемый файл, а не только те, которые в нем неразрешены (поскольку их нет)
  • При загрузке библиотеки с помощью dlopen() добавьте опцию RTLD_GLOBAL , чтобы убедиться, что символы, экспортируемые testc, также доступны для testd

В общем случае gcc не поддерживает RTTI через границы dlopen. У меня есть личный опыт с этой путаницей try / catch, но ваша проблема выглядит примерно так же. К сожалению, я боюсь, что вам нужно придерживаться простых вещей по всему dlopen.

Я должен добавить к этому вопросу, так как я также столкнулся с этой проблемой.

Даже при предоставлении -Wl,-E и использовании RTLD_GLOBAL, dynamic_casts все равно не удалось. Однако передача -Wl,-E в реальной связи приложения, а не только в библиотеке, похоже, исправила это.

Если у вас нет контроля над источником основного приложения,- Wl, - E не применимо. Передача-Wl, - E компоновщику при построении собственных двоичных файлов (хоста так и плагинов) тоже не помогает. В моем случае единственным рабочим решением было загрузить и выгрузить мой хост so из функции _init самого хоста so, используя флаг RTLD_GLOBAL (см. код ниже). Это решение работает в обоих случаях:

  1. основные ссылки приложения против хоста так.
  2. основные нагрузки приложения хост поэтому использует dlopen (без RTLD_GLOBAL).

В обоих случаях необходимо следовать инструкциям, изложенным в GCC visibility wiki.

Если сделать символы плагина и Хоста видимыми друг для друга (с помощью #pragma GCC visibility push/pop или соответствующего атрибута) и загрузить плагины (с хоста so) с помощью RTLD_GLOBAL 1. будет работать также без загрузки и выгрузки собственных так (как указано по ссылке, приведенной выше). Это решение составляет 2. тоже работа, которой раньше не было.


// get the path to the module itself
static std::string get_module_path() {
    Dl_info info;
    int res = dladdr( (void*)&get_module_path, &info);
    assert(res != 0); //failure...

    std::string module_path(info.dli_fname);
    assert(!module_path.empty()); // no name? should not happen!
    return module_path;
}

void __attribute__ ((constructor)) init_module() {
    std::string module = get_module_path();

    // here the magic happens :)
    // without this 2. fails
    dlclose(dlopen(module.c_str(), RTLD_LAZY | RTLD_GLOBAL));
}