3

boost-python を使用して、レガシー C++ ライブラリを Python と統合しています。従来のライブラリにはグローバルな初期化がいくつかあり、その中のクラスはアプリケーション全体のデータを使用します。ラップされたすべてのオブジェクトが破棄された後にレガシー ライブラリのシャットダウン関数が呼び出されるようにする必要があり、これは atexit を使用してシャットダウン関数を登録することで実現できると考えました。しかし、atexit がシャットダウン関数を呼び出した後、ラップされたオブジェクトがクリーンアップされ、レガシー ライブラリ内で複数のセグメンテーション違反が発生することがわかりました。

終了する前にラップされたオブジェクトで del を呼び出すことで目的の動作を実現できますが、削除を Python に任せたいと考えていました。オブジェクトのドキュメントにある赤い警告ボックスを確認しました。__del__、私の理想の世界は到達できないのだろうかと思っています。

Python モジュールでレガシー コードをラップするときに、すべてのオブジェクトがクリーンアップされた後にシャットダウン メソッドが確実に呼び出されるようにするための提案はありますか?

重要な場合のプラットフォームの詳細:

  • パイソン 2.7.2
  • ビジュアル スタジオ 2013
  • 64 ビット ビルド

最小限のコード:

#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 を使用して実行し、次の入力と出力を表示できます。

>>> レガシー
legacy::initializeをインポート
>>> test = legacy.Test()
>>> ^Z
legacy::shutdown
legacy::Test::~Test

4

1 に答える 1

3

つまり、コンストラクターとデストラクターでレガシー ライブラリを初期化およびシャットダウンするガード タイプを作成し、公開された各オブジェクトのスマート ポインターを介してガードを管理します。


破壊プロセスを正しく行うことを困難にする微妙な詳細がいくつかあります。

  • のオブジェクトおよびモジュール内のオブジェクトの破棄の順序Py_Finalize()はランダムです。
  • モジュールのファイナライズはありません。特に、動的にロードされた拡張モジュールはアンロードされません。
  • レガシー API は、それを使用しているすべてのオブジェクトが破棄された後にのみシャットダウンする必要があります。ただし、オブジェクト自体はお互いを認識していない場合があります。

これを実現するために、Boost.Python オブジェクトは、レガシー API をいつ初期化してシャットダウンするかを調整する必要があります。これらのオブジェクトには、レガシー API を使用するレガシー オブジェクトに対する所有権も必要です。単一責任の原則を使用して、責任をいくつかのクラスに分割できます。

Resource Acquisition Is Initialization (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 ガードを解放する必要があります。これを実現する非侵入型の方法の 1 つは、従来の型を Boost.Python に公開するときにカスタムのHeldTypeを提供することです。型を公開するときは、カスタム ファクトリ関数を使用してオブジェクトの作成を制御するため、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;
  }
}

デモンストレーションについては、こちらを参照してください。

于 2015-07-16T03:08:41.573 に答える