Advertisement

Using boost::shared_ptr with AngelScript

Started by July 16, 2010 03:35 AM
5 comments, last by WitchLord 12 years, 10 months ago
Hi Andreas,

since I already got valuable feedback from you about the string issue (thread link) I wanted to start the discussion about another topic as well: "boost::shared_ptr". We use shared pointers in our application a lot, e.g. as return values of manager objects or as parameters to function calls. To make the application interface scriptable we decided to try wrapping the "boost::shared_ptr" class in AngelScript. What I came up with look like the following:

First a wrapper for constructing and desctructing shared pointer objects is defined
/// \brief Wrapper template for shared pointer behavior.template <typename Type>struct SharedPointerWrapper{    /// \brief Constructor wrapper.    static void construct(void* memory);    /// \brief Destructor wrapper.    static void destruct(void* memory);};template <typename Type>void SharedPointerWrapper<Type>::construct(void* memory){    new(memory) boost::shared_ptr<Type>();}template <typename Type>void SharedPointerWrapper<Type>::destruct(void* memory){    ((boost::shared_ptr<Type>*)memory)->~shared_ptr();}


Then I have a registration function, which looks as follows. Note that shared pointers are registered as value type with constructor/destructor and assignment operators. Finally, also a "get" method is provided to retrieve a reference to the contained object. The boost implementation of the get method just returns the raw pointer.
/// \brief Function template for registering shared pointer types.template <typename Type>void RegisterSharedPointer(const Core::String& ptrTypeName, const Core::String& typeName, asIScriptEngine* engine){    Core::String getSignature;    getSignature.append(typeName).append("& get()");    int r;    r = engine->RegisterObjectType(ptrTypeName.c_str(), sizeof(boost::shared_ptr<Type>), asOBJ_VALUE | asOBJ_APP_CLASS_CDA); assert(r >= 0);    r = engine->RegisterObjectBehaviour(ptrTypeName.c_str(), asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(SharedPointerWrapper<Type>::construct), asCALL_CDECL_OBJLAST); assert(r >= 0);    r = engine->RegisterObjectBehaviour(ptrTypeName.c_str(), asBEHAVE_DESTRUCT, "void f()", asFUNCTION(SharedPointerWrapper<Type>::destruct), asCALL_CDECL_OBJLAST); assert(r >= 0);    r = engine->RegisterObjectMethod(ptrTypeName.c_str(), (ptrTypeName + "& opAssign(const " + ptrTypeName + " ∈ other)").c_str(), asMETHODPR(boost::shared_ptr<Type>, operator=, (boost::shared_ptr<Type> const &), boost::shared_ptr<Type>&), asCALL_THISCALL); assert(r >= 0);    r = engine->RegisterObjectMethod(ptrTypeName.c_str(), getSignature.c_str(), asMETHOD(boost::shared_ptr<Type>, get), asCALL_THISCALL); assert(r >= 0);}


Now I can register "boost::shared_ptr" classes as follows:
RegisterSharedPointer<Type>("TypePtr", "Type", engine);


Before executing that line I have to make sure that the type "Type" has been registered on the engine. Since I only want AngelScript code to remember the shared pointer object I currently register the contained type as "asOBJ_REF | asOBJ_SCOPED", i.e.:
engine->RegisterObjectType("Type", 0, asOBJ_REF | asOBJ_SCOPED);engine->RegisterObjectMethod("Type", "string getProperty()", ...);


This finally allows me to write the following in AngelScript:
TypePtr shared = manager.getSomeSharedTypeInstance();print(shared.get().getProperty());


So far the concept does not seem to crash or memory-leak. It also seems to be possible to pass shared pointer instances allocated by AngelScript back to CPP, i.e.
anotherManager.use(shared);


I was wondering what you think about this issue.

Regards,
Georg
I really like this way.
I would soon have the same problem when exposing the UI System to angelscript: Every widget is stored in a shared_ptr and I thought how I could expose them anyway, without giving the scripter the ability to screw up and keep references that are long gone and invalid.

Maybe one could register the shared_ptr as a template, but I haven't worked with templates so far, so I can't be sure if and how that would work.

Advertisement
This looks correct to me.

I'll add a link to this thread on the wiki, for easy reference to others.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Hi WitchLord,

Can you imagine a way to automatically expose the calls of the internal object through the SharedPointer interface?

In code:

ObjectPtr obj;
obj.ptr().doSomething(); // the ptr() is somehow cumbersome here
// obj.doSomething(); // this would be nice but...

We already tried some alternative e.g. making the function "ptr()" a property "ptr" to avoid at least the brackets but thats still not as good as it should be.

Any ideas how to achieve such a behaviour generically and automatically (and safely ;-) )?

xad
For that it would be necessary to register wrapper functions, on the smart pointer interface itself. Maybe something like this:

void ObjectPtr_Method(boost::shared_ptr &ptr){  // If no object is stored in the shared pointer this must not be executed  if( !ptr )  {    asIScriptContext *ctx = asGetActiveContext();    ctx->SetException("Null pointer");    return;  }  // Execute the method on the ptr  ptr->Method();}


Register the function as a method, but with the asCALL_CDECL_OBJLAST calling convention.

It should be possible to create these wrapper methods automatically through templates. Perhaps the code in the autowrapper add-on can be adapted for this purposes.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Sorry to bring back the dead topic, but I was having an issue with type registration as you show using asOBJ_REF | asOBJ_SCOPED. This required me to register some additional behaviors for "Type". I changed to using asOBJ_NOHANDLE instead of asOBJ_SCOPED, and the requirement for those behaviors went bye-bye, but the code still works when passing back and forth to the application.

Also it should be noted you need a factory (not the behavior) for "TypePTR" to get an instance from the application for using in the script. You do touch on the factory by just having it instantiate it in the script via a function.

If I am missing something important by not going the scoped route please let me know now.
Advertisement
You may want to take a look at the code that SiCrane posted in this other thread:

http://www.gamedev.net/topic/617111-more-angelscript-binding-wrappers/




The NOHANDLE flag is meant for uninstanciable types, e.g singletons. The SCOPED type is meant for types with specific memory management requirements but that should behave like value types.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

This topic is closed to new replies.

Advertisement