Jump to content
  • entries
    17
  • comments
    28
  • views
    27256

Interfacing C++ and Python (Part 1)

Sign in to follow this  
Nit

1473 views

I've recently decided it was time to gain a bit more flexibility in my c++ codebase by adding support for a scripting interface. My initial use case was to enable my homegrown gui system (in c++) to offload callbacks and general customization to a scripting layer, rather than having to hard code a user interface in C++. I chose Python as my scripting language, due to my years of experience with this language.

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).
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

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
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!