Archived

This topic is now archived and is closed to further replies.

boost.python: exposing an instance

This topic is 5241 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

class Interface{ public: Clock* clock; Camera* camera; ... }; Interface systems; //this is global and extern How do I expose my Interface instance to python using boost? I will never need to create a new instance, I just need to use this one. I thought about doing object o = (class_(...))(); but that only exposes the class, not the instance. Thanks. Dustin

Share this post


Link to post
Share on other sites
First you need to expose the class, just so the Python interpreter knows a) what's correct and what's not, and b) what to call and when. Therefore, do the whole

BOOST_PYTHON_MODULE(...)
{
class_("Interface")
...
;
}

thing first. Then, do

object o = object( systems );


/* the next line is to call a function with your object
instance as parameter. if you want the object to be in the
global namespace of the particular module, try adding it to the
module's global dictionary, and then in each function doing the
whole 'global systems' dance to get the global variable. I'm
not sure if that approach will work however, but I know the one
below will. I'm NOT however, sure that that's the right
syntax, I just wrote it up from memory here, so check it out. */


PyObject_CallFunction( YourFunc, "O", object.ptr() );


And *poof*, no problems. Tell us if this is what you wanted; if not, we'll help some more.

EDIT: aesthetic errors

Chris Pergrossi
My Realm | "Good Morning, Dave"

[edited by - c t o a n on August 6, 2003 8:55:44 AM]

Share this post


Link to post
Share on other sites
I tried to implement the idea you demonstrated. In the following python script, katana is a staticly linked module and GetInterface returns my Interface instance. The way I call scripts is through a (python) scheduler which calls next() on each loaded script (each script provides a generator called 'main').

When I try to access my instance through python my app crashes.

from __future__ import generators #python 2.3 won't work with boost yet
print "test_interface.py> loaded script"

import katana
print "after import katana"

def main():
print "script> Main"
print katana.GetInterface() #prints "<katana.Interface object at 0x099868B4<"
print "never gets here"
yield None

and my boost:ython ststic module katana definition

#ifndef __PYKATANA_HPP
#define __PYKATANA_HPP

#include <boost/python.hpp>
//#include <boost/python/object.hpp>

//#include <boost/python/def.hpp>

using namespace boost::Python;
#include "interface.hpp"
extern Interface systems;

object GetInterface() {return object(systems);}
BOOST_PYTHON_MODULE(katana)
{
class_<Interface>( "Interface", init<>() )
.def_readonly( "clock", &Interface::clock );

class_<Clock>( "Clock", init<>() )
.def( "GetTimeSincecLastTick", &Clock::GetTimeSinceLastTick )
.def( "GetTime", &Clock::GetTime )
.def( "GetExactTime", &Clock::GetExactTime )
.def( "GetAvgFps", &Clock::GetAvgFps )
.def( "GetExactFps", &Clock::GetExactFps )
.def( "GetElapsedTimeSec", &Clock::GetElapsedTimeSec )
.def( "GetStartTime", &Clock::GetStartTime )
.def( "GetFramecount", &Clock::GetFramecount );

//def( "GetInterface", GetInterface, return_value_policy<reference_existing_object>() ); //this doesn't compile, but I think this is the key to my problem

def( "GetInterface", GetInterface );
}
#endif //__PYKATANA_HPP



Elsewhere in my program I call 'initkatana()'.
What am I doing wrong?

ctoan: I have no idea how to gow about with your other suggestion

try adding it to themodule's global dictionary, and then in each function doing thewhole 'global systems' dance to get the global variable.

Could you get me started? Ideally, I'd expose only the member variables of systems (so I'd do 'katana.clock' as opposed to 'katana.systems.clock'). And if I don't modify each instance (expose them readonly), it doesn't have to be global in each function, right?

Thanks for your help.

PS: ctoan, I was looking through old forum posts and you posted one of your early embedding attempts for someone else's reference. (it had class CBlah) You had 'static CBlah something("Joe");' in it, but you never exposed something to python - did you do this in a later trial? That is exactly what I'm trying to do.

EDIT: goddamn bbcodes

[edited by - thedustbustr on August 7, 2003 7:46:18 PM]

[edited by - thedustbustr on August 7, 2003 7:47:39 PM]

[edited by - thedustbustr on August 7, 2003 7:51:19 PM]

Share this post


Link to post
Share on other sites
Oh, ok, you're talking about a static class and you just want to expose that to Python? Like a singleton or similar? OH, well that's not so complicated after all! Anyway, this is how it works. You have a singleton class (in my case, I've got a regular class with a static object that I retrieve using a static 'get' function, but I'll also explain how to do it using all static functions) that you expose to Python, like so:

// here's my class

class CFoo
{
private:
// some data member

char Bar[ 32 ]
public:
// some functions...

void Set( char* NewBar );
const char* Get();
};

// here's my static object

static CFoo myFoo;

// and here's a function to return the object.

// in my engine, I have this located inside the class

// (I use a variant of superpig's singleton class in his

// enginuity articles), though you can have it outside as well,

// it doesn't make a difference


CFoo& getFoo()
{
return myFoo;
}

// here's our boost definition. notice how if a function is

// either static (and located inside a class) or just a function,

// you just put a 'def' without putting a class_<> template.

// we use 'reference_existing_object' because it's a singleton

// and we don't want copies of it floating around (though boost

// doesn't do a very good job of explaining these return policies

// does it?)

BOOST_PYTHON_MODULE(foo)
{
class_<CFoo>( "CFoo", init<>() )
.def( "Set", &CFoo::Set ) # this COULD be a 'property'...
.def( "Get", &CFoo::Get ) # since it's 'const', we don't need a return policy
;
def( "getFoo", getFoo, return_value_policy<reference_existing_object>() );
}

And that's how you expose the class to python. Now don't forget, if you're embedding Python in the same application as this is being exposed (i.e. you're NOT exporting a seperate DLL), remember to 'initfoo' in your main() function so Python can see it. You only use the Boost :: Python 'object' class to encapsulate PyObject*'s, like when you're trying to pass objects into functions as arguments. If you're returning objects from functions then you don't have to worry about the conversion.

Oh yeah, and for the Python script file, you'd have something like this (just made it up, don't expect it to run):

import foo

myFoo = foo.getFoo()
myFoo.Set( "Chris Wuz Here" )
print myFoo.Get() # should print 'Chris Wuz Here'


And there you have it! Exposing and Retrieving singleton classes in a nutshell. Good luck!

EDIT: you're desire for legibility is also achieved, by doing this:

# get the clock
myClock = systems.getClock()
...
# use the clock
myClock.Blah()

Or at least, I think that's legible

Chris Pergrossi
My Realm | "Good Morning, Dave"

[edited by - c t o a n on August 8, 2003 6:08:43 AM]

Share this post


Link to post
Share on other sites
where you do
static CFoo myFoo; 
can that be extern as well? I got compiler errors rtying to say
extern static Interface systems; 


I suppose I could make Interface a singleton (using superpigs method) and not have to make it extern. The whole idea of an Interface class was to enhance readability by not using singletons *sigh*

Thanks for the help, I''ll be spending the next 6 hours of my life implementing this

Dustin

Share this post


Link to post
Share on other sites
Arrgh... and its only been 40 minutes
The relevant source:

extern Interface systems;
Interface & GetInterface() {return systems;}

BOOST_PYTHON_MODULE(katana)
{
class_<Interface>( "Interface", init<>() )
.def_readonly( "clock", &Interface::clock )
//.def_readonly( "camera", &Interface::camera )

;

class_<Clock>( "Clock", init<>() )
.def( "GetTimeSincecLastTick", &Clock::GetTimeSinceLastTick )
.def( "GetTime", &Clock::GetTime )
.def( "GetExactTime", &Clock::GetExactTime )
.def( "GetAvgFps", &Clock::GetAvgFps )
.def( "GetExactFps", &Clock::GetExactFps )
.def( "GetElapsedTimeSec", &Clock::GetElapsedTimeSec )
.def( "GetStartTime", &Clock::GetStartTime )
.def( "GetFramecount", &Clock::GetFramecount );

def( "GetInterface", GetInterface, return_value_policy<reference_existing_object>() );
//def( "GetInterface", GetInterface );

}

The compiler error:

Compiling...
script.cpp
c:\utilities\boost_1_30_0\boost\python\object\inheritance.hpp(48) : warning C4541: 'dynamic_cast' used on polymorphic type 'class Clock' with /GR-; unpredictable behavior may result
c:\utilities\boost_1_30_0\boost\python\object\inheritance.hpp(46) : while compiling class-template member function 'struct std::pair<void *,struct boost::python::type_info> __cdecl boost::python::objects::polymorphic_id_generator<class Clock
>::execute(void *)'
c:\utilities\boost_1_30_0\boost\python\object\inheritance.hpp(48) : warning C4541: 'typeid' used on polymorphic type 'class Clock' with /GR-; unpredictable behavior may result
c:\utilities\boost_1_30_0\boost\python\object\inheritance.hpp(46) : while compiling class-template member function 'struct std::pair<void *,struct boost::python::type_info> __cdecl boost::python::objects::polymorphic_id_generator<class Clock
>::execute(void *)'
c:\utilities\boost_1_30_0\boost\mpl\if.hpp(88) : fatal error C1076: compiler limit : internal heap limit reached; use /Zm to specify a higher limit
c:\utilities\boost_1_30_0\boost\mpl\if.hpp(88) : see reference to class template instantiation 'boost::mpl::if_c<0,struct boost::python::detail::specify_a_return_value_policy_to_wrap_functions_returning<void>,struct boost::python::to_python_
value<void> >::answer<`template-parameter513'>' being compiled
c:\utilities\boost_1_30_0\boost\python\default_call_policies.hpp(59) : see reference to class template instantiation 'boost::mpl::if_c<0,struct boost::python::detail::specify_a_return_value_policy_to_wrap_functions_returning<void>,struct boo
st::python::to_python_value<void> >' being compiled
c:\utilities\boost_1_30_0\boost\mpl\aux_\preprocessed\msvc60\apply.hpp(41) : see reference to class template instantiation 'boost::python::default_result_converter::apply<void>' being compiled
c:\utilities\boost_1_30_0\boost\mpl\aux_\preprocessed\msvc60\apply.hpp(54) : see reference to class template instantiation 'boost::mpl::aux::msvc_apply1<struct boost::python::default_result_converter>::result_<void>' being compiled
c:\utilities\boost_1_30_0\boost\python\detail\caller.hpp(53) : see reference to class template instantiation 'boost::mpl::apply1<struct boost::python::default_result_converter,void>' being compiled
c:\utilities\boost_1_30_0\boost\python\detail\caller.hpp(148) : see reference to class template instantiation 'boost::python::detail::select_result_converter<struct boost::python::default_call_policies,void>' being compiled
c:\utilities\boost_1_30_0\boost\python\detail\caller.hpp(145) : while compiling class-template member function 'struct _object *__thiscall boost::python::detail::caller_arity<1>::impl<void (__cdecl*)(struct _object *),struct boost::python::d
etail::args_from_python,struct boost::python::default_call_policies,struct boost::mpl::list2<void,struct _object *> >::operator ()(struct _object *,struct _object *)'
Error executing cl.exe.

Those first two warnings worry me. And about the stack limit - is this really a stack limit or is the compiler going bonkers trying to compile this? In the event that it is real, how do you specify command line options "/Zm200" in the Visual c++ 6 gui?

If I comment out the line
def( "GetInterface", GetInterface, return_value_policy() );  
and uncomment the following line, the compiler heap limit goes away and a different boost error occurs (something like you can't have the function return a reference w/o special policies).

Thanks for walking me through this.

edit: a few more details

[edited by - thedustbustr on August 8, 2003 11:03:04 AM]

[edited by - thedustbustr on August 8, 2003 11:04:29 AM]

Share this post


Link to post
Share on other sites
The first error, the one with 'polymorphic types' is because Boost :: Python is trying to determine polymorphic class types at runtime (using the 'typeid()' operator). Therefore, you have to specify the /GR (BOTH letters are capital! It DOES make a difference!) switch along with the /Zm (again case sensitive) one to increase the stack size. To add these switches (in the VC++ 6.0 GUI, which is what I use as well), go to Project->Settings->C/C++ and look at the very bottom. You should see a box at the bottom, and you enter your options there. Check out the screenshot for exact details, the flags you want to enter have been highlighted (though you might want to change the 200 [for 200% memory] to 150 or so).

Check It Out Here

But if you look inside the Boost implementation of all these easy-to-read definitions and stuff (like BOOST_PYTHON...), you'll see that it's VERY advanced and VERY memory intensive C++. For example, you must have noticed, if you do something that Boost doesn't expect, it won't even compile properly. Like if I don't state a return policy when there SHOULD be one, it comes up with a template class like 'specify_a_return_policy_with_functions_returning< char* >: no constructors specified'!??! Why can't I ever get my code to talk to me like that?

EDIT: stupid HTML, not as clean and simple as C++...

Chris Pergrossi
My Realm | "Good Morning, Dave"

[edited by - c t o a n on August 9, 2003 4:22:49 AM]

Share this post


Link to post
Share on other sites
YAY! it worked. Thanks a million! I was trying to set command line options specific for each file using custom building and it wasn''t pretty.

I had to do a little bit of wrapping (for some reason boost couldn''t handle my pointers to system objects) so I made a function that turned the pointer into a reference and passed that to boost. All working great

To solve the readibility problem was as simple as saying

run_string("import katana");
run_string("katana.clock=katana.GetClock()");

I dont know why this worked (I expected the scripts to have a brand new katana namespace when they imported katana) but I guess you can do that with static modules. Anyway, very clean

Thanks
Dustin

ps: the screenshot was nice

Share this post


Link to post
Share on other sites