Я интегрирую устаревшую библиотеку C ++ с Python, используя boost-python. Унаследованная библиотека имеет некоторую глобальную инициализацию, и затем классы в ней используют данные всего приложения. Мне нужно убедиться, что функция shutdown старой библиотеки вызывается после того, как все обернутые объекты уничтожены, и подумал, что это может быть достигнуто путем регистрации функции shutdown с помощью atexit. Однако я обнаружил, что обернутые объекты очищаются после того, как atexit вызывает функцию shutdown, вызывая множественные ошибки в устаревшей библиотеке!
Я могу добиться желаемого поведения, вызывая del на обернутых объектах перед выходом, но надеялся оставить удаление на Python. Я проверил красное окно предупреждения в документация объекта.__del__, и мне интересно, недоступен ли мой идеальный мир.
Какие-либо предложения по обеспечению того, чтобы метод завершения вызывался после очистки всех объектов при переносе устаревшего кода в модуль python?
Некоторые детали платформы на случай, если они важны:
Минимальный код:
#include <iostream>
#include <boost/python.hpp>
using namespace std;
namespace legacy
{
void initialize() { cout << "legacy::initialize" << endl; }
void shutdown() { cout << "legacy::shutdown" << endl; }
class Test
{
public:
Test();
virtual ~Test();
};
Test::Test() { }
Test::~Test() { cout << "legacy::Test::~Test" << endl; }
}
BOOST_PYTHON_MODULE(legacy)
{
using namespace boost::python;
legacy::initialize();
class_<legacy::Test>("Test");
def("_finalize", &legacy::shutdown);
object atexit = object(handle<>(PyImport_ImportModule("atexit")));
object finalize = scope().attr("_finalize");
atexit.attr("register")(finalize);
}
После компиляции это может быть выполнено с использованием python с отображением следующих входных и выходных данных:
>>> импортировать наследие
наследие :: инициализации
>>> test = legacy.Test ()
>>> ^ Z
наследство :: выключение
наследие :: Тест :: ~ Test
Вкратце, создайте тип защиты, который будет инициализировать и завершать работу устаревшей библиотеки в ее конструкторе и деструкторе, а затем управлять защитой с помощью интеллектуального указателя в каждом экспонируемом объекте.
Есть некоторые тонкие детали, которые могут усложнить процесс уничтожения:
Py_Finalize()
является случайнымДля этого объекты Boost.Python должны координировать, когда нужно инициализировать и завершать работу устаревшего API. Эти объекты также должны владеть устаревшим объектом, который использует устаревший API. С использованием принцип единой ответственности, Можно разделить обязанности на несколько классов.
Можно использовать приобретение ресурсов является инициализацией (RAII) идиома для инициализации и выключения устаревшего AP. Например, со следующим legacy_api_guard
, когда legacy_api_guard
Объект создан, он будет инициализировать устаревший API. Когда legacy_api_guard
объект уничтожен, он закроет устаревший API.
/// @brief Guard that will initialize or shutdown the legacy API.
struct legacy_api_guard
{
legacy_api_guard() { legacy::initialize(); }
~legacy_api_guard() { legacy::shutdown(); }
};
Поскольку нескольким объектам необходимо совместно управлять, когда нужно инициализировать и завершать работу устаревшего API, можно использовать умный указатель, такой как std::shared_ptr
, чтобы нести ответственность за управление охраной. Следующий пример лениво инициализирует и завершает работу устаревшего API:
/// @brief Global shared guard for the legacy API.
std::weak_ptr<legacy_api_guard> legacy_api_guard_;
/// @brief Get (or create) guard for legacy API.
std::shared_ptr<legacy_api_guard> get_api_guard()
{
auto shared = legacy_api_guard_.lock();
if (!shared)
{
shared = std::make_shared<legacy_api_guard>();
legacy_api_guard_ = shared;
}
return shared;
}
Наконец, фактический тип, который будет встроен в объект Boost.Python, должен получить дескриптор унаследованной защиты API перед созданием экземпляра унаследованного объекта. Кроме того, после уничтожения устаревшая защита API должна быть освобождена после уничтожения устаревшего объекта. Один ненавязчивый способ сделать это состоит в том, чтобы использовать HeldType при предоставлении устаревших типов в Boost.Python. При представлении типа необходимо отключить сгенерированные по умолчанию инициализаторы Boost.Python, поскольку вместо них будет использоваться настраиваемая фабричная функция для обеспечения контроля над созданием объекта:
/// @brief legacy_object_holder is a smart pointer that will hold
/// legacy types and help guarantee the legacy API is initialized
/// while these objects are alive. This smart pointer will remain
/// transparent to the legacy library and the user-facing Python.
template <typename T>
class legacy_object_holder
{
public:
typedef T element_type;
template <typename... Args>
legacy_object_holder(Args&&... args)
: legacy_guard_(::get_api_guard()),
ptr_(std::make_shared<T>(std::forward<Args>(args)...))
{}
legacy_object_holder(legacy_object_holder& rhs) = default;
element_type* get() const { return ptr_.get(); }
private:
// Order of declaration is critical here. The guard should be
// allocated first, then the element. This allows for the
// element to be destroyed first, followed by the guard.
std::shared_ptr<legacy_api_guard> legacy_guard_;
std::shared_ptr<element_type> ptr_;
};
/// @brief Helper function used to extract the pointed to object from
/// an object_holder. Boost.Python will use this through ADL.
template <typename T>
T* get_pointer(const legacy_object_holder<T>& holder)
{
return holder.get();
}
/// Auxiliary function to make exposing legacy objects easier.
template <typename T, typename ...Args>
legacy_object_holder<T>* make_legacy_object(Args&&... args)
{
return new legacy_object_holder<T>(std::forward<Args>(args)...);
}
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
python::class_<
legacy::Test, legacy_object_holder<legacy::Test>,
boost::noncopyable>("Test", python::no_init)
.def("__init__", python::make_constructor(
&make_legacy_object<legacy::Test>))
;
}
Вот полный пример демонстрирующий использование настраиваемого HeldType для ненавязчивой защиты ресурса с общим управлением:
#include <iostream> // std::cout, std::endl
#include <memory> // std::shared_ptr, std::weak_ptr
#include <boost/python.hpp>
/// @brief legacy namespace that cannot be changed.
namespace legacy {
void initialize() { std::cout << "legacy::initialize()" << std::endl; }
void shutdown() { std::cout << "legacy::shutdown()" << std::endl; }
class Test
{
public:
Test() { std::cout << "legacy::Test::Test()" << std::endl; }
virtual ~Test() { std::cout << "legacy::Test::~Test()" << std::endl; }
};
void use_test(Test&) {}
} // namespace legacy
namespace {
/// @brief Guard that will initialize or shutdown the legacy API.
struct legacy_api_guard
{
legacy_api_guard() { legacy::initialize(); }
~legacy_api_guard() { legacy::shutdown(); }
};
/// @brief Global shared guard for the legacy API.
std::weak_ptr<legacy_api_guard> legacy_api_guard_;
/// @brief Get (or create) guard for legacy API.
std::shared_ptr<legacy_api_guard> get_api_guard()
{
auto shared = legacy_api_guard_.lock();
if (!shared)
{
shared = std::make_shared<legacy_api_guard>();
legacy_api_guard_ = shared;
}
return shared;
}
} // namespace
/// @brief legacy_object_holder is a smart pointer that will hold
/// legacy types and help guarantee the legacy API is initialized
/// while these objects are alive. This smart pointer will remain
/// transparent to the legacy library and the user-facing Python.
template <typename T>
class legacy_object_holder
{
public:
typedef T element_type;
template <typename... Args>
legacy_object_holder(Args&&... args)
: legacy_guard_(::get_api_guard()),
ptr_(std::make_shared<T>(std::forward<Args>(args)...))
{}
legacy_object_holder(legacy_object_holder& rhs) = default;
element_type* get() const { return ptr_.get(); }
private:
// Order of declaration is critical here. The guard should be
// allocated first, then the element. This allows for the
// element to be destroyed first, followed by the guard.
std::shared_ptr<legacy_api_guard> legacy_guard_;
std::shared_ptr<element_type> ptr_;
};
/// @brief Helper function used to extract the pointed to object from
/// an object_holder. Boost.Python will use this through ADL.
template <typename T>
T* get_pointer(const legacy_object_holder<T>& holder)
{
return holder.get();
}
/// Auxiliary function to make exposing legacy objects easier.
template <typename T, typename ...Args>
legacy_object_holder<T>* make_legacy_object(Args&&... args)
{
return new legacy_object_holder<T>(std::forward<Args>(args)...);
}
// Wrap the legacy::use_test function, passing the managed object.
void legacy_use_test_wrap(legacy_object_holder<legacy::Test>& holder)
{
return legacy::use_test(*holder.get());
}
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
python::class_<
legacy::Test, legacy_object_holder<legacy::Test>,
boost::noncopyable>("Test", python::no_init)
.def("__init__", python::make_constructor(
&make_legacy_object<legacy::Test>))
;
python::def("use_test", &legacy_use_test_wrap);
}
Интерактивное использование:
>>> import example
>>> test1 = example.Test()
legacy::initialize()
legacy::Test::Test()
>>> test2 = example.Test()
legacy::Test::Test()
>>> test1 = None
legacy::Test::~Test()
>>> example.use_test(test2)
>>> exit()
legacy::Test::~Test()
legacy::shutdown()
Обратите внимание, что базовый общий подход также применим к не ленивому решению, где устаревший API инициализируется после импорта модуля. Нужно было бы использовать shared_ptr
вместо weak_ptr
и зарегистрируйте функцию очистки с помощью atexit.register()
:
/// @brief Global shared guard for the legacy API.
std::shared_ptr<legacy_api_guard> legacy_api_guard_;
/// @brief Get (or create) guard for legacy API.
std::shared_ptr<legacy_api_guard> get_api_guard()
{
if (!legacy_api_guard_)
{
legacy_api_guard_ = std::make_shared<legacy_api_guard>();
}
return legacy_api_guard_;
}
void release_guard()
{
legacy_api_guard_.reset();
}
...
BOOST_PYTHON_MODULE(example)
{
// Boost.Python may throw an exception, so try/catch around
// it to initialize and shutdown legacy API on failure.
namespace python = boost::python;
try
{
::get_api_guard(); // Initialize.
...
// Register a cleanup function to run at exit.
python::import("atexit").attr("register")(
python::make_function(&::release_guard)
);
}
// If an exception is thrown, perform cleanup and re-throw.
catch (const python::error_already_set&)
{
::release_guard();
throw;
}
}
Увидеть Вот для демонстрации.