With the GUI/addon system use case in mind, I need my C++ code to call into a Python script/module and I also need my Python scripts to interact with C++ objects. Here's a simple example that I've got in mind for this process:
# import the pyd that interfaces python with my c++ library
from az_gui import *
...
def onCancelButton(self, event):
'''
Event handler for cancel button.
'''
window = self.getWindow()
window.close()
return True
I've decided to split this topic into several postings. This first post covers the ground work necessary for calling into Python scripts. The second posting will review a PythonCallback wrapper class that is designed to encapsulate the Python C API code for calling function callbacks in python modules. After that I'll have a post or two on my Boost.Python experiences that expose my C++ libraries to Python (e.g., allowing for Python code to call the C++ class, Window as shown in the example above).
C++ calls into Python scripts:
Went with the Python C API for communication in this direction, since it was a manageable process. I'm resistant to adding 3rdParty dependencies to my project, so this seemed like the best I could do. On a side note, my original goal was to write my own Python C API wrappers to also exposing my c++ classes to Python scripts, but that panned out to be an enormous undertaking that I'm not ready to tackle yet. (this discussion is saved for another post).
There were a few catches along the way that made the implementation less straightfoward. For starters, a standard install of Python does not have a debug library. As such, some macro magic needs to be performed to include Python.h header file.
//Note
// Since Python may define some pre-processor definitions which affect
// the standard headers on some systems, you must include Python.h before
// any standard headers are included.
#ifdef _DEBUG
#undef _DEBUG
#include "Python.h"
#define _DEBUG
#else
#include "Python.h"
#endif
It is my preference to keep this Python.h ugliness inside the cpp files, so my next trick involved forward declaring PyObject:
// 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
Pulling it all together:
To manage the Python C API, I created a PythonManager class to encapsulate the Python interpreter session used within my codebase.
PythonManager.h
...
class PythonManager
{
public:
PythonManager();
virtual ~PythonManager();
void initialize();
void uninitialize();
// add directories to the python interpreter's sys.path
// to allow for Python scripts to locate script directories.
bool addSysPath(const std::string& relativePath);
protected:
};
PythonManger.cpp:
...
PythonManager::PythonManager()
{
}
PythonManager::~PythonManager()
{
uninitialize();
}
bool PythonManager::addSysPath(const std::string& relativePath)
{
std::ostringstream ss;
ss << "import os\n";
ss << "import sys\n";
ss << "olderr = sys.stderr\n";
ss << "oldout = sys.stdout\n";
ss << "sys.stdout = open('pythonOut.txt','w')\n";
ss << "print 'cwd is %s' % os.getcwd()\n";
ss << "print 'adding relative path to sys.path: %s' % '" << relativePath.c_str() << "'\n";
ss << "sys.path.append(os.path.join(os.getcwd(), '" << relativePath << "'))\n";
ss << "sys.stderr = olderr\n";
ss << "sys.stdout = oldout\n";
int retval = PyRun_SimpleString(ss.str().c_str());
if (retval != 0)
{
PyErr_Print();
}
return retval == 0;
}
void PythonManager::initialize()
{
Py_Initialize();
}
void PythonManager::uninitialize()
{
Py_Finalize();
}
Finally, this PythonManager is called from outside my "main loop":
PythonManager* python = new PythonManager();
python->initialize();
python->addSysPath("../scripts");
Check back for follow on summaries on the remaining implementation details (PythonCallback wrapper class, and Boost Python).