[C++-sig] wrapping interfaces and supplied implementations

Brian Peters topology at eircom.net
Fri Jan 16 00:26:00 CET 2004


Hello All,

I'd like first to thank the Boost.Python team and commend them on their
creation. Dave Abrahams et.al. have implemented code the likes of which
I could not even have conceived, let alone writen. Boost MPL is not
something I could ever have thought up. My hope is simply that someone
some day finds my work as useful and impressive as I find theirs.

I am trying to wrap a library that implements a generic programming
approach to computational geometry and topology. To achieve this it
defines a number of interfaces it requires to do its job. For developers
who do not wish to code the details of these interfaces it also supplies
concrete implementations.


Having observed Dave Abrahams' penchant for a working test case I have
developed a sandbox representing a portion of the library. It has three
classes that are abstract base  classes: IFacade, IManager, and
IProduct. All their pure virtual methods are defined using raw pointers
to abstract base classes.

The idea is that an application asks the facade for a manager [
IManager* IFacade::manager() ] and then uses the manager to create
products [ IProduct* IManager::create(inti i) ]. The products have state
that may be changed by the client or other operations of the facade. The
manager can also store and retrieve products.

A simple user can use the concrete Manager and Product classes supplied
through these interfaces, but a more advanced user could derive their
own product and manager from the abc's and pass that into the facade [
void IFacade::setManager(IManager* manager) ]. The old addage applies:
"The simple should be easy, but the complex should be possible." i.e use
the supplied system if you want, but you can always over ride it with
something more complex.

So now I want to be able to do the same thing in Python. I want to wrap
everything so that a simple user can:
* see the abstract base classes in python;
   i.e. the interfaces, their doc strings, etc.
* use the supplied implementation of manager and product
* derive their own products and manager if they want/need to in python

Well, I got it all working! Not only working, but doing cool python
stuff too. Being new to python I have found it interesting that
arbitrary attributes and methods can be added to any object regardless
of its class.

I think of a wrapped Product passed out to python from the supplied
Manager class as a python envelope containing the C++ Product. I can
scribble on that envelope. Spill coffee on it. Crumple it up a bit. I
can store it in the manager and throw it away. When I later retrieve
from the manager, not only is it the same C++ Product (i.e same
address), but it is the same PyObject (i.e. same address, or "id" as
they call it in the interpreter), AND it even has the same note
scribbled on it and the coffee stain! Its like decorating C++ classes
dynamically at run time.

I thought the article about C++/Python integration was cool when I read
it a few months ago, but now I really see it. The line between Python
and C++ is very fluid. (
http://www.cuj.com/documents/s=8188/cuj0307abrahams )

The code is attached below. For reference I am using the following
(admittedly somewhat dated) environment:
*  Python 2.2.2
*  Boost 1.30.0
*  Windows NT4 with MSVC6
*  RH linux 2.4.2-2 with g++ 1.96

I am offering this example to anyone that finds it useful. It is more
complex than the examples I found in the documentation and code, yet
still simple enough to examine in detail.

I am hoping that those in the know will tell me if I have done anything
terribly wrong. I will gladly explain why I have done things the way I
have and/or share the verbose version of this sandbox that contains a
lot more testing functions and cout's. This message is already too long
to do so here.

thanks,
bfp

======================
> Brian Peters
>  topology<at>eircom<dot>net

===============================================================================

============ abcdf_ext.cpp
====================================================

#include <boost/python.hpp>
#include <iostream>
#include <sstream>

using namespace boost::python;

// --- component code -----------------------------------------------
// published interfaces
struct IProduct
{
  virtual ~IProduct() {}
  virtual int value() const = 0;
  virtual void set(int i) = 0;
  virtual int other(IProduct* product) const = 0;
};

struct IManager
{
  virtual ~IManager() {}
  virtual IProduct* create(int i) = 0;
  virtual IProduct* modify(IProduct* product) = 0;
  virtual int store(IProduct* product) = 0;
  virtual IProduct* retrieve(int index) = 0;
};

struct IFacade
{
  static IFacade* instance();
  virtual ~IFacade() {}
  virtual IManager* manager() = 0;
  virtual void setManager(IManager* manager) = 0;
  virtual void update(int i) = 0;
  virtual std::string notice() const = 0;
};

// supplied implementation of interfaces.
// only exposed through pointers to the above interfaces.
#define MANAGER_CAPACITY 10
struct Product : IProduct
{
  Product(int i): _i(i) {}
  virtual ~Product() {}
  virtual int value() const
    {
    return _i;
    }
  virtual void set(int i)
    {
    _i = i;
    }
  virtual int other(IProduct* product) const
    {
    int v = -10; // TODO Should I throw if NULL == product???
    if (product)
      v = product->value();
    return _i*1000 + v;
    }
  int _i;
};

struct Manager : IManager
{
  Manager(): mCount(0)
    {
    memset(mProducts, 0, sizeof(mProducts));
    }
  virtual ~Manager() {}
  virtual IProduct* create(int i)
    {
    return new Product(i);
    }
  virtual IProduct* modify(IProduct* product)
    {
    product->set(2 * product->value());
    return product;
    }
  virtual int store(IProduct* product)
    {
    int index = -1;
    if (MANAGER_CAPACITY > mCount)
      {
      index = mCount;
      mProducts[mCount++] = product;
      }
    return index;
    }
  virtual IProduct* retrieve(int index)
    {
    IProduct* product = NULL;
    if (MANAGER_CAPACITY > index)
      product = mProducts[index];
    return product;
    }
  int mCount;
  IProduct* mProducts[MANAGER_CAPACITY];
};

struct Facade : IFacade
{
  Facade(): mManager(new Manager) {}
  virtual ~Facade() {}
  virtual IManager* manager()
    {
    return mManager;
    }
  virtual void setManager(IManager* manager)
    {
    // TODO Deleting the old manager will cause trouble if it has
    // already been sent out to python. How do I want to handle this?
    //if (mManager)
    //  delete mManager;
    mManager = manager;
    }
  virtual void update(int delta)
    {
    // TODO get a product iterator from IManager here.
    IProduct* product;
    for (int i=0; i < MANAGER_CAPACITY; i++)
      {
      if (NULL != (product = mManager->retrieve(i)))
        product->set(product->value() + delta);
      }
    }
  virtual std::string notice() const
    {
    return "IFacade::notice() message: hello world!";
    }
  IManager* mManager;
};

// the singleton
IFacade* theFacade = NULL;
IFacade* IFacade::instance()
  {
  if (NULL == theFacade)
    theFacade = new Facade();
  return theFacade;
  }


// --- B.P.L. wrapper code -------------------------------------------
// rudimentary exception class
struct error
{
  error(std::string message): _msg(message) {}
  std::string const _msg;
};

// ProductProxy provides abc methods to pass IProduct* to python.
struct ProductProxy : IProduct
{
  ProductProxy(IProduct* product) : mProduct(product) {}
  virtual ~ProductProxy() {}
  virtual int value() const
    {
    if (!mProduct)
      throw error("Bad proxy");
    return mProduct->value();
    }
  virtual void set(int i)
    {
    if (!mProduct)
      throw error("Bad proxy");
    mProduct->set(i);
    }
  virtual int other(IProduct* product) const
    {
    if (!mProduct)
      throw error("Bad proxy");
    return mProduct->other(product);
    }
  // this virtual function is overridden in ProductBase
  virtual int otherWrap(object& product) const
    {
    if (!mProduct)
      throw error("Bad proxy");
    ProductProxy* proxy =
arg_from_python<ProductProxy*>(product.ptr())(product.ptr());
    return mProduct->other(proxy);
    }
  IProduct* mProduct;
};

// ProductBase is the heldtype for ProductProxy to let python derive
from IProduct.
struct ProductBase : ProductProxy
{
  ProductBase(PyObject* pyobject): ProductProxy(NULL), mSelf(pyobject)
{}
  virtual ~ProductBase() {}
  virtual int value() const
    {
    return call_method<int>(mSelf, "value");
    }
  int default_value() const
    {
    std::ostringstream msg;
    msg << mSelf->ob_type->tp_name << " missing implementation of
function 'value'";
    throw error(msg.str());
    }
  virtual void set(int i)
    {
    call_method<void>(mSelf, "set", i);
    }
  void default_set(int i)
    {
    std::ostringstream msg;
    msg << mSelf->ob_type->tp_name << " missing implementation of
function 'set'";
    throw error(msg.str());
    }
  virtual int other(IProduct* product) const
    {
    ProductProxy proxy(product);
    return call_method<int>(mSelf, "other", ptr(&proxy));
    }
  // this virtual function is overridden from ProductProxy
  virtual int otherWrap(object& product) const
    {
    return call_method<int>(mSelf, "other", product);
    }
  int default_other(object& product) const
    {
    std::ostringstream msg;
    msg << mSelf->ob_type->tp_name << " missing implementation of
function 'other'";
    throw error(msg.str());
    }
  PyObject* mSelf;
};

// ManagerProxy provides abc methods to pass IManager* to python.
struct ManagerProxy : IManager
{
  ManagerProxy(IManager* manager): mManager(manager), mCount(0) {}
  ManagerProxy(ManagerProxy& manager)
  : mManager(manager.mManager), mCount(manager.mCount)
    {
    for (int i=--mCount; 0 <= i; --i)
      mProducts[i] = manager.mProducts[i];
    }
  virtual ~ManagerProxy() {}
  virtual IProduct* create(int i)
    {
    if (!mManager)
      throw error("Bad proxy");
    return mManager->create(i);
    }
  // this virtual function is overridden in ManagerBase
  virtual object createWrap(int i)
    {
    if (NULL == mManager)
      throw error("Bad proxy");
    return object(ptr(new ProductProxy(mManager->create(i))));
    }
  virtual IProduct* modify(IProduct* product)
    {
    if (NULL == mManager)
      throw error("Bad proxy");
    return mManager->modify(product);
    }
  // this virtual function is overridden in ManagerBase
  virtual object modifyWrap(object& product)
    {
    if (NULL == mManager)
      throw error("Bad proxy");
    ProductProxy* proxy =
arg_from_python<ProductProxy*>(product.ptr())(product.ptr());
    mManager->modify(proxy);
    return product;

    // IManager::modify returns its argument, but if another function
might not, do this:
    //IProduct* value = mManager->anotherFunction(proxy);
    //return value == proxy ? product : object(ptr(new
ProductProxy(value)));
    }
  virtual int store(IProduct* product)
    {
    return mManager->store(product);
    }
  virtual int storeWrap(object& product)
    {
    ProductProxy* proxy =
arg_from_python<ProductProxy*>(product.ptr())(product.ptr());

    // make sure side effects will happen
    mManager->store(proxy->mProduct);

    int index = -1;
    if (MANAGER_CAPACITY > mCount)
      {
      index = mCount;
      mProducts[mCount++] = product;
      }
    return index;
    }
  virtual IProduct* retrieve(int index)
    {
    return mManager->retrieve(index);
    }
  virtual object retrieveWrap(int index)
    {
    // make sure side effects will happen
    mManager->retrieve(index);

    if (MANAGER_CAPACITY > index)
      return mProducts[index];
    else
      return object(); // python's None
    }
  IManager* mManager;

  // just mirror the Manager approach to storage for sandbox
  int mCount;
  object mProducts[MANAGER_CAPACITY];
};

// ManagerBase is the heldtype for ManagerProxy to let python derive
from IManager.
struct ManagerBase : ManagerProxy
{
  ManagerBase(PyObject* pyobject): ManagerProxy(NULL), mSelf(pyobject)
{}
  virtual ~ManagerBase() {}
  virtual IProduct* create(int i)
    {
    object product = call_method<object>(mSelf, "create", i);
    return arg_from_python<ProductProxy*>(product.ptr())(product.ptr());

    }
  // this virtual function is overridden from ManagerProxy
  virtual object createWrap(int i)
    {
    return call_method<object>(mSelf, "create", i);
    }
  object default_create(int i)
    {
    std::ostringstream msg;
    msg << mSelf->ob_type->tp_name << " missing implementation of
function 'create'";
    throw error(msg.str());
    }
  virtual IProduct* modify(IProduct* product)
    {
    ProductProxy proxy(product);
    return call_method<IProduct*>(mSelf, "modify", ptr(&proxy));
    }
  // this virtual function is overridden from ManagerProxy
  virtual object modifyWrap(object& product)
    {
    return call_method<object>(mSelf, "modify", product);
    }
  object default_modify(object& product)
    {
    std::ostringstream msg;
    msg << mSelf->ob_type->tp_name << " missing implementation of
function 'modify'";
    throw error(msg.str());
    }
  virtual int store(IProduct* product)
    {
    ProductProxy proxy(product);
    return call_method<int>(mSelf, "store", ptr(&proxy));
    }
  virtual int storeWrap(object& product)
    {
    return call_method<int>(mSelf, "store", product);
    }
  int default_store(object& product)
    {
    std::ostringstream msg;
    msg << mSelf->ob_type->tp_name << " missing implementation of
function 'store'";
    throw error(msg.str());
    }
  virtual IProduct* retrieve(int index)
    {
    object product = call_method<object>(mSelf, "retrieve", index);
    return arg_from_python<ProductProxy*>(product.ptr())(product.ptr());

    }
  virtual object retrieveWrap(int index)
    {
    return call_method<object>(mSelf, "retrieve", index);
    }
  object default_retrieve(int index)
    {
    std::ostringstream msg;
    msg << mSelf->ob_type->tp_name << " missing implementation of
function 'retrieve'";
    throw error(msg.str());
    }
  PyObject* mSelf;
};

// after IFacade, but no need to inherit
struct FacadeProxy
{
  ~FacadeProxy() {}
  static FacadeProxy* instance()
    {
    return new FacadeProxy();
    }
  object managerWrap()
    {
    if (mManager == object())
      mManager = object(ptr(new
ManagerProxy(IFacade::instance()->manager())));
    return mManager;
    }
  void setManagerWrap(object manager)
    {
    ManagerProxy* proxy =
arg_from_python<ManagerProxy*>(manager.ptr())(manager.ptr());
    if (NULL != proxy)
      {
      mManager = manager;
      IFacade::instance()->setManager(proxy);
      }
    else
      {
      std::ostringstream msg;
      msg << "Object type: " << manager.ptr()->ob_type->tp_name << " not
derived from IManager";
      throw error(msg.str());
      }
    }
  void update(int delta)
    {
    IFacade::instance()->update(delta);
    }
  std::string notice() const
    {
    return IFacade::instance()->notice();
    }
  object mManager;
};

// exception handling
void translate(error const& e)
  {
  PyErr_SetString(PyExc_AttributeError, e._msg.c_str());
  }

// utility functions
PyObject* _last;
void dumpLast()
  {
  std::cout << "dumpLast" << std::endl;
  std::cout << "    last: " << (void*)_last << " [" <<
(typeid(*_last)).name() << "]" << std::endl;
  std::cout << "    last->ob_refcnt: " << _last->ob_refcnt << std::endl;

  std::cout << "    last->ob_type->tp_name: " << _last->ob_type->tp_name
<< std::endl;
  }
void dumpPyObject(PyObject* pyobject)
  {
  _last = pyobject; // steal for dumpLast
  std::cout << "dumpPyObject" << std::endl;
  std::cout << "    pyobject: " << (void*)pyobject << " [" <<
(typeid(*pyobject)).name() << "]" << std::endl;
  std::cout << "    pyobject->ob_refcnt: " << pyobject->ob_refcnt <<
std::endl;
  std::cout << "    pyobject->ob_type->tp_name: " <<
pyobject->ob_type->tp_name << std::endl;
  }
void dumpProduct(ProductProxy* proxy)
  {
  std::cout << "dumpProduct" << std::endl;
  std::cout << "    proxy: " << (void*)proxy << " [" <<
(typeid(*proxy)).name() << "]" << std::endl;
  ProductBase* base = dynamic_cast<ProductBase*>(proxy);
  if (base)
    {
    std::cout << "    proxy->mSelf: " << (void*)base->mSelf << " [" <<
(typeid(*base->mSelf)).name() << "]" << std::endl;
    std::cout << "    proxy->mSelf->ob_refcnt: " <<
base->mSelf->ob_refcnt << std::endl;
    std::cout << "    proxy->mSelf->ob_type->tp_name: " <<
base->mSelf->ob_type->tp_name << std::endl;
    }
  else
    {
    std::cout << "    proxy->mProduct: " << (void*)proxy->mProduct << "
[" << (typeid(*proxy->mProduct)).name() << "]" << std::endl;
    }
  }

int useValue_PyObject_ptr(PyObject* product)
  {
  return call_method<int>(product, "value");
  }
int useValue_object_ref(object& product)
  {
  return call_method<int>(product.ptr(), "value");
  }
int useValue_ProductProxy_ptr(ProductProxy* product)
  {
  return NULL != product ? product->value() : -1;
  }

ProductProxy* passBack_ProductProxy_ptr(ProductProxy* product)
  {
  return product;
  }
object passBack_object(object& product)
  {
  ProductProxy* proxy =
arg_from_python<ProductProxy*>(product.ptr())(product.ptr());
  if (NULL == proxy)
    return object();
  else
    return product;
  }

BOOST_PYTHON_MODULE(abcdf_ext)
{
  register_exception_translator<error>(&translate);

  class_<ProductProxy, ProductBase, boost::noncopyable>("IProduct")
      .def("value", &ProductProxy::value, &ProductBase::default_value,
"Returns an int value")
      .def("set", &ProductProxy::set, &ProductBase::default_set, "Sets
an int value")
      .def("other", &ProductProxy::otherWrap,
&ProductBase::default_other, "Returns an int value using another
IProduct")
      ;

  class_<ManagerProxy, ManagerBase, boost::noncopyable>("IManager")
      .def("create", &ManagerProxy::createWrap,
&ManagerBase::default_create)
      .def("modify", &ManagerProxy::modifyWrap,
&ManagerBase::default_modify)
      .def("store", &ManagerProxy::storeWrap,
&ManagerBase::default_store)
      .def("retrieve", &ManagerProxy::retrieveWrap,
&ManagerBase::default_retrieve)
      ;

  class_<FacadeProxy>("IFacade", no_init)
      .def("instance", &FacadeProxy::instance,
           return_value_policy<manage_new_object>())
 // How do I do a singleton?
      .staticmethod("instance")
      .def("manager", &FacadeProxy::managerWrap)
      .def("setManager", &FacadeProxy::setManagerWrap)
 // ,with_custodian_and_ward<1, 2>())
 // I don't think I need this as returning an object keeps track of
reference counts.
      .def("update", &FacadeProxy::update)
      .def("notice", &FacadeProxy::notice)
      ;

  def("dumpLast", &dumpLast);
  def("dumpPyObject", &dumpPyObject);
  def("dumpProduct", &dumpProduct);

  def("useValue_PyObject_ptr", &useValue_PyObject_ptr);
  def("useValue_object_ref", &useValue_object_ref);
  def("useValue_ProductProxy_ptr", &useValue_ProductProxy_ptr);

  def("ProductProxy_ptr_manage_new_object", &passBack_ProductProxy_ptr,
      return_value_policy<manage_new_object>());
  def("ProductProxy_ptr_reference_existing_object",
&passBack_ProductProxy_ptr,
      return_value_policy<reference_existing_object>());
  def("Product_as_object", &passBack_object);
      // no return_value_policy needed

}

#include "../../../boost_1_30_0/libs/python/test/module_tail.cpp"

============ abcdf_ext.cpp
====================================================

# test abcdf_ext: Abstract Base Class with DeFaults
from abcdf_ext import *

class Derived(IProduct):
  def __init__(self, i):
    IProduct.__init__(self)
    self.i = i
  def value(self):
    return self.i
  def set(self, i):
    self.i = i
  def other(self, product):
    return self.i*1000 + product.value()

def callValue(product):
  return product.value()

def callOther(product, other):
  return product.other(other)

def callSet(product, i):
  product.set(i)

class DManager(IManager):
  def __init__(self):
    IManager.__init__(self)
    self._products = []
  def create(self, i):
    return Derived(i)
  def modify(self, product):
    product.set(product.value()*3)
    return product
  def store(self, product):
    index = len(self._products)
    self._products.append(product)
    return index
  def retrieve(self, index):
    try:
      return self._products[index]
    except:
      return None

E=IFacade.instance()
M=E.manager()
D=DManager()

m = M.create(2)
n = M.create(3)

m._m = "m"
n._n = "n"

i1 = M.store(m)
i2 = M.store(n)
i3 = M.store(M.create(4))

M.retrieve(i1)._m

d = D.create(4)

E.setManager(D)
Dr=E.manager()
e=Dr.create(5)
f=Dr.create(6)

e._e = "e"
f._f = "f"

Dr.store(e)
Dr.store(f)

===============================================================================

===============================================================================







More information about the Cplusplus-sig mailing list