[C++-sig] Re: Contributing an embedding example (after a bugfix ;)

thor.arne.johansen at ibas.no thor.arne.johansen at ibas.no
Wed Dec 4 17:18:25 CET 2002




>thor.arne.johansen at ibas.no writes:
>
>
[Snip]
>
>> I'm wondering if it is appropriate to post the example here for review
or a
>> (reasonable?) example of embedding with Boost.Python. The example is
about
>> 350 lines (including the test script)?
>
>Sure! Maybe you and Dirk can collaborate over what makes a good
>embedding example. Two heads are better than one, as they say.
>
>You might consider getting Jeremy Siek to set you guys up with write
>access to the Boost CVS sandbox, so you can develop your code/docs
>there. However, if that's too much mental overhead, doing it on this
>list should be OK.


I think I will stay out of the sandbox for now :), but I'm posting my
example code below. The hope of course is that I will get some useful
comments/tips (on error/exception handling??), and that the example might
be useful to others:

***** Begin: main.cpp *****

//
// Embedding and extending with Boost.Python example
// By Thor Arne Johansen
//
#include <iostream>
#include <string>
#include <cstdio>
#include <conio.h>

#include <boost/python/class.hpp>
#include <boost/python/def.hpp>
#include <boost/python/str.hpp>
#include <boost/python/extract.hpp>
#include <boost/python/module.hpp>
#include <boost/python/call_method.hpp>
#include <boost/python/handle.hpp>

// Error codes
#define Success         0
#define PY_InitError    -100
#define PY_ConvertError -101
#define PY_CallError    -102

// Global module pointer
boost::python::handle<> module;

// Class definitions

//
// PythonException: Exception object thrown by Python interface (usually
caused by an exception in Python interpreter)
//
class PythonException
{
    std::string     m_exception_type;
    std::string     m_error_message;

public:
    PythonException():m_exception_type(""),m_error_message(""){};

    void setExceptionType(std::string msg) {m_exception_type = msg;}
    void setErrorMessage(std::string msg) {m_error_message = msg;}
    std::string getExceptionType() {return m_exception_type;}
    std::string getErrorMessage(void) {return m_error_message;}
};

//
// Test class which contain state
//
class MyClass
{
private:
    _int64 m_state;

public:
    MyClass():m_state(0){}
    MyClass(_int64 st):m_state(st){}

    void    setState(_int64 st) { m_state = st;}
    _int64  getState() { return m_state;}
    void    printPtr() { printf("C++   : Ptr=0x%p", this); }
};

// Function prototypes
int     initPython(std::string scriptName);
int     callPython(std::string func, std::string msg,
boost::python::object& obj);
void    getExceptionDetail(PythonException& exc);

// Convenience functions
boost::python::handle<>
import_module(std::string module_name)
{
    using namespace boost::python;

    PyObject *module_ptr =
PyImport_ImportModule(const_cast<char*>(module_name.c_str()));
    if( module_ptr == NULL)
    {
        PythonException exc;
        getExceptionDetail(exc);
        throw(exc);
    }
    return handle<>(module_ptr);
}

boost::python::object
get_function(const char *func_name)
{
    PyObject* dict = PyModule_GetDict(module.get());        // Borrowed
reference
    boost::python::str funcName(func_name);
    return boost::python::object(boost::python::handle
<>(PyDict_GetItem(dict, funcName.ptr())));
}

// Python wrapper for MyClass
BOOST_PYTHON_MODULE(myCppModule)
{
    using namespace boost::python;

    class_<MyClass>("MyClass")
        .def("getState", &MyClass::getState)
        .def("setState", &MyClass::setState)
        .def("printPtr", &MyClass::printPtr)
        ;
}

// Call a function in module (ptrPyModule) with two arguments
// func:    Name of python function
// msg:     Message string
// obj:     Boost.Python object (an instance of a wrapped class)
int callPython(const std::string funcStr, std::string msg,
boost::python::object& obj)
{
    int                 retv;
    PythonException     p_exc;      // Create empty exception object on
stack

    try
    {
        boost::python::object func = get_function(const_cast<char
*>(funcStr.c_str()));
        retv = boost::python::extract<int>(func(msg, obj));
    }
    catch(...)          // What should we catch here??
    {
        getExceptionDetail(p_exc);
        throw(p_exc);
    }
    return retv;
}

//
//
// Initialize Python interpreter
// Initialize Extension module that expose C++ classes/objects to Python
script
// Set up sys.path to include script path (Where the python scripts can be
found)
//
//
int initPython(std::string scriptName)
{
    if(scriptName.length() == 0 )
        return PY_InitError;

    // Register the extension module
    PyImport_AppendInittab("myCppModule", initmyCppModule);

    // Initialize the Python interpreter
    Py_Initialize();

    // Set up so sys.path contain our script path
    std::string pyinitstr(
        "import sys\n"
        "sys.path.append(\"##SCRIPTPATH##\")\n"
        "\n"
    );

    // Build full path to scripts
    // For now, we hardcode the path where the scripts are located
    // Typically this would be read from a configuration file
    std::string scr_path = "d:/work/ExtendEmbedd2";

    // Insert path into init string
    std::string placeHolder("##SCRIPTPATH##");
    unsigned int pos = pyinitstr.find(placeHolder);
    if(pos!=std::string::npos)
    {
        pyinitstr.erase(pos, placeHolder.length());
        pyinitstr.insert(pos, scr_path);
    }

    // Run the init script to set up sys.path
    if( PyRun_SimpleString(const_cast<char*>(pyinitstr.c_str())) != NULL )
        std::cout << "C++   : Warning: Could not set up sys.path\n" <<
std::endl;

    // Import the module, storing the pointer in a handle<>
    try
    {
        module = import_module(const_cast<char*>(scriptName.c_str()));
    }
    catch(PythonException& exc)
    {
        std::cout << "\nC++   : Exception thrown when importing module in
initPython():" << std::endl;
        std::cout << "C++   :    " << exc.getExceptionType() << ": " <<
exc.getErrorMessage() << std::endl;
        return PY_InitError;
    }
    catch(...)
    {
        std::cout << "C++   : Unknown exception when importing module " <<
scriptName << std::endl;
        return PY_InitError;
    }
    return Success;
}

// Complete the exception object exc based on the exception thrown by the
Python module
// This was adopted from pyerrors.c found here:
http://rmi.net/~lutz/errata-supplements.html
void getExceptionDetail(PythonException& exc)
{
    PyObject*   exc_type;
    PyObject*   exc_value;
    PyObject*   exc_traceback;
    PyObject*   pystring;

    PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
    if( exc_type==0 && exc_value==0 && exc_traceback==0)
    {
        exc.setExceptionType("Strange: No Python exception occured");
        exc.setErrorMessage("Strange: Nothing to report");
    }
    else
    {
        pystring = NULL;
        if (exc_type != NULL &&
           (pystring = PyObject_Str(exc_type)) != NULL &&     /*
str(object) */
           (PyString_Check(pystring))
           )
            exc.setExceptionType(PyString_AsString(pystring));
        else
            exc.setExceptionType("<unknown exception type>");

        Py_XDECREF(pystring);

        pystring = NULL;
        if (exc_value != NULL &&
           (pystring = PyObject_Str(exc_value)) != NULL &&     /*
str(object) */
           (PyString_Check(pystring))
           )
            exc.setErrorMessage(PyString_AsString(pystring));
        else
            exc.setErrorMessage("<unknown exception data>");

        Py_XDECREF(pystring);

        Py_XDECREF(exc_type);
        Py_XDECREF(exc_value);         /* caller owns all 3 */
        Py_XDECREF(exc_traceback);     /* already NULL'd out */
    }
}

//
// Initialize, and run some tests
//
int main(int argc, char *argv[])
{
    std::cout << "C++   : Initializing Python and loading script" <<
std::endl;
    int retv = initPython("myscript");
    if( retv == Success )
    {
        // Create a MyClass, and set some state information
        MyClass     mc(10);

        // Show identity
        mc.printPtr(); std::cout << std::endl;

        // Show state
        std::cout << "C++   : mc.getState() in C++ before Python call: "
<< mc.getState() << std::endl;

        // Do some stuff to the object from Python

        // First wrap a reference to mc in an bpl object
        boost::python::object arg2(boost::ref(mc));

        // Show identity of the wrapped object
        arg2.attr("printPtr")();std::cout << std::endl;

        // Alternative method to do the same thing
        boost::python::call_method<void>(arg2.ptr(),"printPtr"); std::cout
<< std::endl;

        try
        {
            // Call Python, possibly changing the state of mc
            int retv = callPython("myTestFunc", "String argument from c++",
arg2);
            std::cout << "C++   : callPython() returned: " << retv <<
std::endl;
        }
        catch(PythonException& exc)
        {
            std::cout << "\nC++   : Exception thrown in callPython():" <<
std::endl;
            std::cout << "C++   :    " << exc.getExceptionType() << ": "
<< exc.getErrorMessage() << std::endl;
            retv = PY_CallError;
        }
        catch(...)
        {
            std::cout << "C++   : Unknown exception when calling
callPython!" << std::endl;
            retv = PY_CallError;
        }

        // Show state
        std::cout << "C++   : mc.getState() after Python call:  " <<
mc.getState() << std::endl;
    }
    else
        std::cout << "C++   : Failed to initialize!" << std::endl;

    std::cout << "C++   : Press any key to quit" << std::endl;
    getch();

    return retv;
}

***** End:  main.cpp *****

***** Begin: myscript.py *****

try:
     import myCppModule
     print "Python: Import SUCCEEDED!!!"
except:
     print "Python: Import FAILED..."

#
# Test module
# Illustrate both instatiation and use of already instantiated C++ objects
#
def myTestFunc(msg, cppObj):
     # Print message from C++
     print "Python: msg = ", msg

     # Instantiate a MyClass object
     a = myCppModule.MyClass()

     # Show identity
     print "Python: Identity of 'local' MyClass object: ",
     a.printPtr()
     print

     # Change state of object
     print "Python: Current state:", a.getState()
     a.setState(1234)
     print "Python: New state    :", a.getState()

     # Delete object
     del a

     # Test the object exposed from c++ (Passed as a formal parameter)
     # Show identify of cppObj
     print "Python: Identity of exposed cppObj: ",
     cppObj.printPtr()
     print

     # Show state of cppObj
     print "Python: cppObj.getState() on entry:", cppObj.getState()

     # Change state of object
     cppObj.setState(-9223000000000000000)         # Test the limits of
long long (or _int64 on msvc)

     # Show state of object
     print "Python: cppObj.getState() on exit :", cppObj.getState()
     return 999

***** End: myscript.py *****


--
Thor Arne Johansen
Dept. Manager R&D
Ibas AS





More information about the Cplusplus-sig mailing list