• entries
    17
  • comments
    28
  • views
    27043

Interfacing C++ and Python (Part 2)

Sign in to follow this  

2356 views

This is the second posting that deals with interfacing C++ with a Python scripting layer. Part 1 can be found here.

The focus in this post relates to the design I chose to follow for wrapping Python callback functions in C++. The design objective here is to ensure that a callback function can be quickly defined in python, while also not requiring any recompilation of the c++ UI library. This last feature is important, because in theory the user interface should be configurable and extendable by advanced end users of the game engine. A lot of my inspiration of this train of thought can be attributed to World of Warcraft's extremely customizable user interface.

To setup this example, consider the following xml file that defines a simple GUI, with a window containing a button. The button defines a callback to be executed when the button is pressed, that links to a function defined in Python.









When constructing this class, a generic interface must be defined that are components of GUI elements. In my codebase, currently all Python standalone functions like this inherit from this callback process.


class PythonCallback
{
public:
PythonCallback(const std::string& module, const std::string& callback);
virtual ~PythonCallback();

protected:
PyObject* _module;
PyObject* _callback;
};


Notice _module and _callback are PyObjects. To rehash from my earlier entry, I personally prefer to forward declare this PyObjects rather than use a #include to ensure that python.h is not included unless it is absolutely necessary (e.g., in the cpp implementation file!).


// forward declare PyObject
// as suggested on the python mailing list
// http://mail.python.org/pipermail/python-dev/2003-August/037601.html
#ifndef PyObject_HEAD
struct _object;
typedef _object PyObject;
#endif


Much of the Python C API setup is done in the constructor of this abstract class. The guts of this is largely derived from the Python C API documentation, particularly in the following tutorial:
http://docs.python.o.../embedding.html

The primary function here is to create the _callback and _module instances and maintain proper memory management of our PyObject references.


PythonCallback::PythonCallback(const std::string &module, const std::string &callback)
{
PyObject *pyName;
pyName = PyString_FromString(module.c_str());

_module = PyImport_Import(pyName);
Py_DECREF(pyName);

if (_module)
{
_callback = PyObject_GetAttrString(_module, callback.c_str());
/* _callback is a new reference */

if (_callback && PyCallable_Check(_callback))
{
// success
}
else
{
if (PyErr_Occurred())
PyErr_Print();
std::cout << "Cannot find function." << std::endl;
}
}
}

PythonCallback::~PythonCallback()
{
if (_module)
{
Py_DECREF(_module);
}

if (_callback)
{
Py_DECREF(_callback);
}
}


Now with the interface defined, we can define Functor's (http://www.parashift....html#faq-33.15) that wrap the more complicated Python C API code. The following is the class declaration of an example PythonCallbackVoid class.


// wraps Python callbacks that require no arguments and return an int
class PythonCallbackVoid : public PythonCallback
{
public:
PythonCallbackVoid(const std::string& module, const std::string& callback);
~PythonCallbackVoid();

virtual int operator()();

protected:
};


Since the PythonCallback base class setups the _callback PyObject, all that is required of the child class is to call the _callback function.


int PythonCallbackVoid::operator ()()
{
if (!_callback)
{
// throw or assert
return 0;
}

PyObject* pyValue; // PyObject that contains the return value.

// call into the callback function
pyValue = PyObject_CallObject(_callback, 0);
long retVal = 0;
if (pyValue)
{
retVal = PyInt_AsLong(pyValue);
std::cout << "Result of call: " << retVal << std::endl;
Py_DECREF(pyValue);
}
else
{
PyErr_Print();
std::cout << "Call failed" << std::endl;
}

return (int)retVal;
}


Pulling it all together:
I'll wrap up this entry with an example to summarize these classes. First, I'll assume that there is a Python module defined in the following directory structure


/bin (working directory)
/scripts
__init__.py
...
/ui
__init__.py (initialize the ui module)
uitest.py
...


If the use of __init__.py seems foreign to you, be sure to check out Python's module documentation: http://docs.python.o...al/modules.html
(This is one of the strengths of Python; their online documentation is outstanding). In a nutshell, the __init__.py files are required to inform Python that the directory contains packages.

Inside uitest.py, a simple function called test:


def test():
print 'ui test successful'
return 42


Now in C++, we can call this function using the new PythonCallbackVoid functor fairly easily:


// Note: Py_Initialized() must be called directly prior to this block of code
// (see PythonManager, documented in Part 1)
PythonCallbackVoid callback("ui.uitest", "test");
int retVal = callback(); // returns 42


Next entry, I'll start discussing Boost.Python, for wrapping C++ classes for use in Python scripts. Thanks for reading.
Sign in to follow this  


2 Comments


Recommended Comments

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now