dimanche 12 décembre 2010

How to use Python to plugin C++

Goal
Plug-ins permit to easily extend a software without modifying it. For the language, Python plug-ins avoid the per platform compilation needed with c++. Actually it does not need compilation.

We'll make a basic sample to call python implemented member function on a python built c++ object.

Prerequisite
CMake will be used to manage project build. I no more able to start a project without it :).

There is multiple way to use Python, for example the official Python C API, I'll use boost 1.42 python.

Files
There will be 3 different "programs" :
  • test executable : it loads the python plug-in, extract and use the corresponding c++ object. It need two arguments : plug-in interface library directory path and python plug-in path
  • plugin interface library : defines the plug-in interface so that it can be inherited in python and used in the c++
  • python plugin
  
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)

#
# Setup depencies
#
 # For python you can't specify the version, so it'll take the default one (for me it's 2.7)
 # set PYTHON_INCLUDE_DIRS and PYTHON_LIBRARIES
find_package( PythonLibs REQUIRED )
 # set PYTHON_LIBRARIES
find_package( Boost 1.42.0 COMPONENTS python )

#
# boost/python.hpp need this to compile
#
include_directories ( ${PYTHON_INCLUDE_DIRS} )

#
#c++ plug-in interface
#
add_library( plugin SHARED "Plugin.hpp" "Plugin.cpp"  )
 # avoid the lib prefix on unix
set_target_properties( plugin PROPERTIES PREFIX "" )
target_link_libraries( plugin ${Boost_LIBRARIES} )

#
#test executable
#
add_executable( test "main.cpp" )
 # need to link with boost python and python
target_link_libraries( test ${Boost_LIBRARIES} ${PYTHON_LIBRARIES} plugin )

Plug-in interface Library
hpp
#include <boost/python.hpp>

namespace bpy = boost::python;

namespace plugin
{
  // Need to inherits from bpy::wrapper
  // to easily access python implementation
  // (see python_virtual definition )
  class Object : public bpy::wrapper<object>
  {
  public:    
    void exported();  // will be accessible from python ( see line 32 )
    void non_exported(); // won't be accessible from python

    void python_virtual(); // will call the python plugin implementation
  };
}

cpp
#include "Plugin.hpp"
#include <iostream>

namespace plugin
{
  void Object::exported()
  {
    std::cout << "exported called " << std::endl;
  }

  void Object::non_exported()
  {
  }

  void Object::python_virtual()
  {
      this->get_override("python_virtual")(); // call the inheriting python object python_virtual member function
  }
}

// Define the python module that will be used
// to encapsulate all exported symbols.
// It must be compiled in a library with this
// exact name ( plugin.so/dll )
BOOST_PYTHON_MODULE(plugin)
{
//All this will be accessible from python  
    boost::python::class_<plugin::Object, boost::noncopyable>("Object")
        .def("exported", &plugin::Object::exported );
    
    // You can export enums with 
    //boost::python::enum_<EnumType>("EnumTypePython")
    //  .value("EnumVal0Python", EnumVal0)
    //  .value("EnumVal1Python", EnumVal1)
    //;
}

Test executable
#include <boost/python.hpp>

#include <string>
#include <iostream>

#include "Plugin.hpp"

int main(int argc, char * argv[] )
{
  if( argc == 3 ) // need two parameters
  {
    std::string const python_interface_path = argv[1];
    std::string const python_plugin_path = argv[2];

    namespace plg = plugin;
    
    try
    {
      Py_Initialize(); // Initialize the python system
      
    //Construct a context for the plugin to run in
      bpy::object main_module = bpy::import( "__main__" );
      bpy::object main_namespace = main_module.attr( "__dict__" );
      // With sys.path setup to be able to import plugin library
      bpy::object sys_module = bpy::import( "sys" );
      bpy::object path = sys_module.attr( "path" );
      path.attr("append")( python_interface_path );
    
    //We execute the plugin in the previously built context
      bpy::exec_file(
         python_plugin_path.c_str(),
         main_namespace
      );    
      
    //And extract the object variable
    //It must be an instance of a plugin::Object child
      plg::Object & object = bpy::extract<plg::Object &>( main_namespace["object"] );

    //Call a "python virtual" function
      object.python_virtual();
    }
    catch( bpy::error_already_set const & )
    {
      PyErr_Print(); // For any boost::python exception the python log will be printed
    }    
  }
}

Python plugin-in
import plugin # import the plugin library

class PluginObject ( plugin.Object ): # inherit from plugin base class
  def __init__(self):
    plugin.Object.__init__(self)
    self.exported()
    #self.non_exported() # can't be called because it have not been exported

  def python_virtual(self) :
    print( "python virtual called" )

# this object variable will be extracted
# in the c++ text executable
object = PluginObject()


This is just a tiny part of what you can do. So, have fun coding your plug-ins ;)

Aucun commentaire:

Enregistrer un commentaire