| AngelScript |

Inheriting from application registered class

A script class cannot directly inherit from an application registered class, as the script classes are not compiled into native machine code like the application classes are.

It is however possible to emulate the inheritance by using a proxy class to hide the underlying differences in an abstract layer. The proxy class has two parts, one is the C++ side that the application sees, and the other is the script side that the scripts can see and inherit from.

The following is an example implementation of such a proxy class.

// On the C++ side
class FooScripted
{
public:
// Public interface that we want the script to be able to override
void CallMe()
{
// If the script side is still alive, then call the scripted function
if( !m_isDead->Get() )
{
asIScriptEngine *engine = m_obj->GetEngine();
asIScriptContext *ctx = engine->RequestContext();
// GetMethodByDecl returns the virtual function on the script class
// thus when calling it, the VM will execute the derived method
ctx->Prepare(m_obj->GetObjectType()->GetMethodByDecl("void CallMe()"));
ctx->SetObject(m_obj);
ctx->Execute();
engine->ReturnContext(ctx);
}
}
int m_value;
// A factory function that can be used by the script side to create
static FooScripted *Factory()
{
asIScriptContext *ctx = asGetActiveContext();
// Get the function that is calling the factory so we can be certain it is the FooScript script class
asIScriptFunction *func = ctx->GetFunction(0);
if( func->GetObjectType() == 0 || std::string(func->GetObjectType()->GetName()) != "FooScripted" )
{
ctx->SetException("Invalid attempt to manually instantiate FooScript_t");
return 0;
}
// Get the this pointer from the calling function so the FooScript C++
// class can be linked with the FooScript script class
asIScriptObject *obj = reinterpret_cast<asIScriptObject*>(ctx->GetThisPointer(0));
return new FooScripted(obj);
}
// Reference counting
void AddRef()
{
m_refCount++;
// Increment also the reference counter to the script side so
// it isn't accidentally destroyed before the C++ side
if( !m_isDead->Get() )
m_obj->AddRef();
}
void Release()
{
// Release the script instance too
if( !m_isDead->Get() )
m_obj->Release();
if( --m_refCount == 0 ) delete this;
}
// Assignment operator
FooScripted &operator=(const FooScripted &o)
{
// Copy only the content, not the script proxy class
m_value = o.m_value;
return *this;
}
protected:
// The constructor and destructor are indirectly called
FooScripted(asIScriptObject *obj) : m_obj(0), m_isDead(0), m_value(0), m_refCount(1)
{
// Get the weak ref flag for the script object to
// avoid holding a strong reference to the script class
m_isDead = obj->GetWeakRefFlag();
m_isDead->AddRef();
m_obj = obj;
}
~FooScripted()
{
// Release the weak ref flag
m_isDead->Release();
}
// Reference count
int m_refCount;
// The C++ side holds a weak link to the script side to
// avoid a circular reference between the C++ side and
// script side
asILockableSharedBool *m_isDead;
asIScriptObject *m_obj;
};

This type is registered with the engine as the following:

void RegisterFooScripted(asIScriptEngine *engine)
{
engine->RegisterObjectType("FooScripted_t", 0, asOBJ_REF);
engine->RegisterObjectBehaviour("FooScripted_t", asBEHAVE_FACTORY, "FooScripted_t @f()", asFUNCTION(FooScripted::Factory), asCALL_CDECL);
engine->RegisterObjectBehaviour("FooScripted_t", asBEHAVE_ADDREF, "void f()", asMETHOD(FooScripted, AddRef), asCALL_THISCALL);
engine->RegisterObjectBehaviour("FooScripted_t", asBEHAVE_RELEASE, "void f()", asMETHOD(FooScripted, Release), asCALL_THISCALL);
engine->RegisterObjectMethod("FooScripted_t", "FooScripted_t &opAssign(const FooScripted_t &in)", asMETHOD(FooScripted, operator=), asCALL_THISCALL);
engine->RegisterObjectMethod("FooScripted_t", "void CallMe()", asMETHOD(FooScripted, CallMe), asCALL_THISCALL);
engine->RegisterObjectProperty("FooScripted_t", "int m_value", asOFFSET(FooScripted, m_value));
}

The script side is declared as shared so it can be used in all script modules. It is also declared as abstract so it cannot be instantiated by itself, only as a parent class of another script class.

This script section should preferably be included automatically by the application in all the modules that should be able to derive from the FooScripted class.

      // On the script side
      shared abstract class FooScripted
      {
        // Allow scripts to create instances
        FooScripted()
        {
          // Create the C++ side of the proxy
          @m_obj = FooScripted_t();

        }

        // The copy constructor performs a deep copy
        FooScripted(const FooScripted &o)
        {
          // Create a new C++ instance and copy content
          @m_obj = FooScripted_t();
          m_obj = o.m_obj;

    "
        }

        // Do a deep a copy of the C++ object
        FooScripted &opAssign(const FooScripted &o)
        {
          // copy content of C++ instance
          m_obj = o.m_obj;
          return this;
        }

        // The script side forwards the call to the C++ side
        void CallMe() { m_obj.CallMe(); }

        // The C++ side property is exposed to the script through accessors
        int m_value
        {
          get { return m_obj.m_value; }
          set { m_obj.m_value = value; }
        }

        // The script class can be implicitly cast to the C++ type through the opImplCast method
        FooScripted_t @opImplCast() { return m_obj; }

        // Hold a reference to the C++ side of the proxy
        private FooScripted_t @m_obj;
      }

Now the scripts classes can derive from the FooScripted class
and access the properties and methods of the parent class normally.

      // Implement a script class that derives from the application class
      class FooDerived : FooScripted
      {
        void CallMe()
        {
           m_value += 1;
        }
      }

      void main()
      {
        // When newly created the m_value is 0
        FooDerived d;
        assert( d.m_value == 0 );

        // When calling the method the m_value is incremented with 1
        d.CallMe();
        assert( d.m_value == 1 );
      }

It is of course also possible to create an instance of the scripted class from the application and access it through the FooScripted C++ proxy, thus making it transparent from the rest of the application that the implementation is actually in the script.

FooScripted *CreateFooDerived(asIScriptEngine *engine)
{
// Create an instance of the FooDerived script class that inherits from the FooScripted C++ class
asIScriptObject *obj = reinterpret_cast<asIScriptObject*>(engine->CreateScriptObject(mod->GetTypeInfoByName("FooDerived")));
// Get the pointer to the C++ side of the FooScripted class
FooScripted *obj2 = *reinterpret_cast<FooScripted**>(obj->GetAddressOfProperty(0));
// Increase the reference count to the C++ object, as this is what will
// be used to control the life time of the object from the application side
obj2->AddRef();
// Release the reference to the script side
obj->Release();
return obj2;
}
void Foo(asIScriptEngine *engine)
{
FooScripted *obj = CreateFooDerived(engine);
// Once the object is created the application can access it normally through
// the FooScripted pointer, without having to know that the implementation
// is actually done in the script.
// When newly created the m_value is 0
assert( obj->m_value == 0 );
// When calling the method the m_value is incremented with 1 by the script
obj->CallMe();
assert( obj->m_value == 1 );
// Release the object to destroy the instance (this will also destroy the script side)
obj->Release();
}
asIScriptEngine::RegisterObjectMethod
virtual int RegisterObjectMethod(const char *obj, const char *declaration, const asSFuncPtr &funcPointer, asDWORD callConv, void *auxiliary=0, int compositeOffset=0, bool isCompositeIndirect=false)=0
Registers a method for the object type.
asILockableSharedBool::AddRef
virtual int AddRef() const =0
Adds a reference to the shared boolean.
asIScriptEngine::RequestContext
virtual asIScriptContext * RequestContext()=0
Request a context.
asBEHAVE_FACTORY
@ asBEHAVE_FACTORY
Factory.
Definition: angelscript.h:362
asBEHAVE_RELEASE
@ asBEHAVE_RELEASE
Release.
Definition: angelscript.h:368
asIScriptContext
The interface to the virtual machine.
Definition: angelscript.h:2717
asIScriptContext::GetFunction
virtual asIScriptFunction * GetFunction(asUINT stackLevel=0)=0
Returns the function at the specified callstack level.
asITypeInfo::GetName
virtual const char * GetName() const =0
Returns a temporary pointer to the name of the datatype.
asIScriptContext::Execute
virtual int Execute()=0
Executes the prepared function.
asIScriptEngine::RegisterObjectType
virtual int RegisterObjectType(const char *obj, int byteSize, asDWORD flags)=0
Registers a new object type.
asOBJ_REF
@ asOBJ_REF
A reference type.
Definition: angelscript.h:250
asIScriptContext::SetObject
virtual int SetObject(void *obj)=0
Sets the object for a class method call.
asIScriptEngine
The engine interface.
Definition: angelscript.h:1092
asIScriptEngine::RegisterObjectBehaviour
virtual int RegisterObjectBehaviour(const char *obj, asEBehaviours behaviour, const char *declaration, const asSFuncPtr &funcPointer, asDWORD callConv, void *auxiliary=0, int compositeOffset=0, bool isCompositeIndirect=false)=0
Registers a behaviour for the object type.
asIScriptObject::GetWeakRefFlag
virtual asILockableSharedBool * GetWeakRefFlag() const =0
Returns the weak ref flag for the object.
asFUNCTION
#define asFUNCTION(f)
Returns an asSFuncPtr representing the function specified by the name.
Definition: angelscript.h:675
asIScriptEngine::CreateScriptObject
virtual void * CreateScriptObject(const asITypeInfo *type)=0
Creates an object defined by its type.
asMETHOD
#define asMETHOD(c, m)
Returns an asSFuncPtr representing the class method specified by class and method name.
Definition: angelscript.h:740
asIScriptEngine::ReturnContext
virtual void ReturnContext(asIScriptContext *ctx)=0
Return a context when it won't be used anymore.
asCALL_THISCALL
@ asCALL_THISCALL
A thiscall class method.
Definition: angelscript.h:232
asILockableSharedBool
A lockable shared boolean.
Definition: angelscript.h:4077
asIScriptFunction
The interface for a script function description.
Definition: angelscript.h:3823
asBEHAVE_ADDREF
@ asBEHAVE_ADDREF
AddRef.
Definition: angelscript.h:366
asIScriptEngine::RegisterObjectProperty
virtual int RegisterObjectProperty(const char *obj, const char *declaration, int byteOffset, int compositeOffset=0, bool isCompositeIndirect=false)=0
Registers a property for the object type.
asIScriptContext::SetException
virtual int SetException(const char *info, bool allowCatch=true)=0
Sets an exception, which aborts the execution.
asIScriptObject
The interface for an instance of a script object.
Definition: angelscript.h:3413
asCALL_CDECL
@ asCALL_CDECL
A cdecl function.
Definition: angelscript.h:226
asIScriptContext::Prepare
virtual int Prepare(asIScriptFunction *func)=0
Prepares the context for execution of the function.
asIScriptObject::Release
virtual int Release() const =0
Decrease reference counter.
asIScriptFunction::GetObjectType
virtual asITypeInfo * GetObjectType() const =0
Returns the object type for class or interface method.
asIScriptContext::GetThisPointer
virtual void * GetThisPointer(asUINT stackLevel=0)=0
Returns a pointer to the object, if a class method is being executed.
asIScriptObject::GetAddressOfProperty
virtual void * GetAddressOfProperty(asUINT prop)=0
Returns a pointer to the property referenced by prop.
asOFFSET
#define asOFFSET(s, m)
Returns the offset of an attribute in a struct.
Definition: angelscript.h:672
asGetActiveContext
AS_API asIScriptContext * asGetActiveContext()
Returns the currently active context.