Making C++ class have python 'member functions'

Started by
2 comments, last by Ezbez 17 years, 6 months ago
See my next post, please. The question has changed, some, but this information may still be helpful. Now, I'm not really looking for actually creating member functions of my C++ class in Python, but I would like to create something similiar. Essentially, I want my C++ class Entity to have some functions like "OnKeyPres()" which then runs a script in Python which modifies the Entity. So, mabye the OnKeyPress script would look something like so:
def Run(self,data):
    if(data.key == LEFT):
        self.AddForce(-1.0,0.0)
    if(data.key == RIGHT):
        self.AddForce(1.0,0.0)
    if(data.key == UP):
        self.AddForce(0.0,1.0)
    if(data.key == DOWN):
        self.AddForce(0.0,-1.0)




So, I've tried out Boost.Python and cooked up some POS code.
void Script::RunScript(Entity *e, EventData *data)
{
	object module(handle<>(borrowed(PyImport_AddModule("__main__"))));
	object dictionary = module.attr("__dict__");
	try
	{
		//Get e and data into Python...
		dictionary["self"] = new Entity(e);//Must be garbage collectable
		dictionary["data"] = data;

		//Run the source
		PyRun_SimpleString(source.c_str());

		PyRun_SimpleString("Run(self,data)");//run.c_str());-+
	}
	catch (error_already_set)
	{
		PyErr_Print();
	}

	Entity* newEntity = extract<Entity*>(dictionary["self"]);
	e->SetSelf(newEntity);
}



Basically, Script is a class that reads in a script from a file, then runs it on a given Entity with some data thrown. This doesn't feel right to me. A couple things are bad: 1. I have to new a copy of e so that it can be GCed by Python. 2. PyErr_Print() doesn't seem to do anything since I don't have a Python window up. 3. It doesn't run the script. 4. I can't just modify e directly, I have to make a copy of it, then modify the copy, then modify e to be like teh copy. Basically, it's a mess, and it doesn't even run the script. I have expose the Entity class and init'ed it. I've checked and it does load the script properly, so it's not that. So, my question is, quite simple really, how the heck do I do this? I'm sure I'm messing something up, because I've only ever used Python as a scripting language in very small test cases before. No turoials I have found have covered much about this with Boost.Python. [Edited by - Ezbez on October 8, 2006 8:44:00 AM]
Advertisement
Let me refine my question some more, since I haven't gotten any replies:

First, I actually have the scripts running now, which is a big improvement. I had forgotten to insert newlines into the file, heh. Python kinda needed those.

With that out of the way, I still have some problems. I still don't like how I have to make a copy of Entity twice for this to work. It's messy, and simply doesn't work so great. Is there any way to give Python a non-Garbage Collected reference to my Entity? Basically, I want to be able to directly change a C++ variable from within Python. Is that even possible?
Quote:Original post by Ezbez
Let me refine my question some more, since I haven't gotten any replies:

First, I actually have the scripts running now, which is a big improvement. I had forgotten to insert newlines into the file, heh. Python kinda needed those.

With that out of the way, I still have some problems. I still don't like how I have to make a copy of Entity twice for this to work. It's messy, and simply doesn't work so great. Is there any way to give Python a non-Garbage Collected reference to my Entity? Basically, I want to be able to directly change a C++ variable from within Python. Is that even possible?


It's definitely possible; Boost.Python is just really poorly documented. :)

Instead of using PyRun_SimpleString(), what you want to do is call your function using boost::python::call<>(), which allows you to pass in an arbitrary number of arguments and even get a return value back from the function. Here is some sample code:

void Script::RunScript(Entity *e, EventData *data){     try     {          PyObject* moduleName = PyString_FromString("OnKeyPress");          PyObject* onKeyPressModule = PyImport_Import(moduleName);          Py_DECREF(moduleName);          //find and call the RunScript function          PyObject *fnRun = PyObject_GetAttrString(onKeyPressModule, "Run");          if(fnRun)          {               boost::python::call<void>(fnRun, boost::python::ptr(e), boost::python::ptr(data));          }          else          {               std::cout << "Couldn't find Run function!\n";          }          Py_DECREF(fnRun);          Py_DECREF(onKeyPressModule);     }     catch( boost::python::error_already_set )     {          PyErr_Print();     }}
Thanks! I understand that code, and it works wonders.

However, it still think that there should be an easier way. If there's some other ways, please let me know!

This topic is closed to new replies.

Advertisement