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));
}