динамическое приведение не удается при использовании с 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 ответа:
Я нашел ответ на свой вопрос здесь. Как я понимаю, мне нужно сделать объект 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 (см. код ниже). Это решение работает в обоих случаях:
- основные ссылки приложения против хоста так.
- основные нагрузки приложения хост поэтому использует 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)); }