Dynamic_cast терпит неудачу при использовании с 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)
  • Пример кода доступен

Ответы

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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

Ответ 4

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

  • основные ссылки приложений на хост.
  • основное приложение загружает хост, используя dlopen (без RTLD_GLOBAL).

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

Если вы делаете символы плагина и хоста настолько видимыми друг другу (используя видимость нажатия кнопки #pragma GCC push/pop или соответствующий атрибут) и загружает плагины (из этого хоста) с помощью 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));
}