Сбой dynamic_cast при использовании с dlopen / dlsym - PullRequest
13 голосов
/ 28 февраля 2010

Введение

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

Настройка

Я определил два интерфейса, 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);

Вопрос * * 1023 Если я создаю свое основное приложение, связываясь с библиотеками 'testc' и 'testd', все работает как положено Однако если я изменю основное приложение, чтобы оно не связывалось с 'testc' и 'testd', а вместо этого загрузило их во время выполнения, используя dlopen / dlsym, тогда dynamic_cast завершится неудачей. Я не понимаю, почему. Любые подсказки? Дополнительная информация

Ответы [ 4 ]

15 голосов
/ 28 февраля 2010

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

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

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

3 голосов
/ 10 марта 2010

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

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

1 голос
/ 20 апреля 2011

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

  1. основное приложение связывается с хостом так.
  2. основное приложение загружает хост, используя 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));
}
...