Typeinfo, разделяемые библиотеки и dlopen() без RTLD_GLOBAL

У меня возникают проблемы с неправильным функционированием исключений (или, по крайней мере, как я надеюсь, я знаю, что есть проблемы с этим) через общие библиотеки при загрузке с помощью dlopen. Здесь я приведу несколько упрощенных примеров кода. Фактическая ситуация - myapp = Matlab, myext1 = расширение mexglx matlab, mylib - это общая библиотека моего кода между двумя расширениями (myext1, myext2)

mylib.h

struct Foo { Foo(int a); m_a; }
void throwFoo();

mylib.cpp

#include "mylib.h"
Foo::Foo(int a): m_a(a) {}
void throwFoo() { throw Foo(123); }

myext1.cpp

#include "mylib.h" 
#include <iostream>
extern "C" void entrypoint()    
{ 
   try { throwFoo(); } 
   catch (Foo &e) { std::cout << "Caught foo\n"; }
}

myext2.cpp Идентично myext1.cpp

myapp.cpp

#include <dlfcn.h>
int main()
{
  void *fh1 = dlopen("./myext1.so",RTLD_LAZY);
  void *fh2 = dlopen("./myext2.so",RTLD_LAZY);
  void *f1  = dlsym(fh1,"entrypoint");
  void *f2  = dlsym(fh2,"entrypoint");
  ((void (*)())func1)();  // call myext1 (A)
  ((void (*)())func2)();  // call myext2 (B)
}

Компиляция этого кода:

g++ mylib.cpp -fPIC  -o libmylib.so -shared
g++ myext1.cpp -fPIC -o myext1.so -shared -L. -lmylib -Wl,-rpath=.
g++ myext2.cpp -fPIC -o myext2.so -shared -L. -lmylib -Wl,-rpath=. 
g++ myapp.cpp -fPIC -o myapp -ldl

Вызов to entrypoint() в A работает, как и ожидалось, с throwFoo(), бросая исключение и entrypoint(), ловя его. Однако вызов B не позволяет устранить исключение. Добавление более диагностического кода показывает, что типinfo для класса Foo отличается в двух расширениях. Изменение порядка двух вызовов dlopen не имеет значения, второе загруженное расширение выходит из строя.

Я знаю, что могу исправить это, используя RTLD_GLOBAL в качестве дополнительного флага для dlopen, но приложение (Matlab), использующее dlopen, вне моего контроля. Есть ли что-нибудь, что я могу сделать с mylib или myext1, myext2, чтобы исправить эту проблему?

Мне нужно избегать использования флагов LD для среды выполнения (поскольку я не могу управлять пользователями, использующими двоичный файл Matlab). Любые другие предложения?

Ответ 1

Правило 62 в "Стандартах кодирования С++" Александреску и Саттера:

"62. Не допускайте распространения исключений через границы модулей."

Хотя он может работать, когда вы делаете это тщательно, для настоящего портативного и многоразового кода это невозможно. Я бы сказал, что это довольно распространенное общее правило при программировании общих библиотек или DLL, не распространяйте исключения по границам модулей. Просто используйте интерфейс C-стиля, верните коды ошибок и сделайте все внутри экспортируемой функции внутри блока try { } catch(...) { };. Кроме того, RTTI не разделяется между модулями, поэтому не ожидайте, что Foo будет иметь один и тот же типinfo в разных модулях.

Ответ 2

Простая обходная задача заключается в том, чтобы ваша библиотека dlopen сама имела флаг RTLD_GLOBAL при первом использовании. Это переопределит предыдущий открытый с помощью RTLD_LOCAL и поместит все в глобальное пространство имен символов.

Ответ 3

Есть ли что-нибудь, что я могу сделать с mylib или myext1, myext2, чтобы исправить это проблема?

В качестве альтернативы использованию RTLD_GLOBAL вы можете просто использовать переменную среды LD_PRELOAD при запуске приложения, чтобы исправить вашу проблему. Вам не нужно перекомпилировать что-либо:

LD_PRELOAD=libmylib.so ./myapp