|
Integrating C++ Objects with ScriptEase and SEObjectWrapper
For those developers working with a project in C++, one of the first questions
is how to make C++ objects available to scripts. The task of integrating numerous
existing objects into the ScriptEase engine can seem daunting. Fortunately,
the object-oriented nature of ScriptEase has a number of similarities that help
map between the two languages. Nombas provides a utility class, the SEObjectWrapper
class, which takes advantage of these similarities and handles most of the integration
to make it simple for the developer.
The SEObjectWrapper class is designed with the following goals in mind:
- Letting the user create new objects from within the ScriptEase engine
- Automatic management of object lifetime and deletion
- Simple method of exporting existing methods to the ScriptEase engine
- Simple method of exporting dynamic properties to the ScriptEase engine
- Ability to create fully dynamic ScriptEase objects easily
- Allowing for any C++ object hierarchy to be represented in ScriptEase
The SEObjectWrapper class is designed to be simple to use, yet powerful enough
to handle all of your needs. With just a few extra lines of code and some wrapper
functions, any class can be converted into a ScriptEase object with minimal
effort.
Using the SEObjectWrapper class, objects can be created either within C++ or
from within the ScriptEase engine. The task of when the object
should be deleted is handled automatically by the wrapper
class. For basic objects, the first section, "Creating
Simple Objects", describes how to make your C++ objects
available to the ScriptEase engine. For objects with dynamic
properties, or when properties need to be exposed to the ScriptEase
engine, there is a SEDynamicObjectWrapper class, which is
described in the section "Creating
Dynamic Objects". The final section of this documentation,
"Advanced Programming Techniques",
describes a number of mechanisms for doing things not possible
with the standard interface, such as containment, controlling
method attributes, lifetime management and inheritance. You
should only need to consult the final section if what you
want to do is not covered in the first two sections.
The files and samples in this folder are shipped with the
ISDK/C 4.40 release, but there is nothing specific in these
files that should prevent them from being modified for 4.30.
The files revered to in this document, along with a sample
MSVC6 project, may be downloaded from: ftp://ftp.nombas.com/pub/isdkeval/se440/jseobject.zip.
1. Creating Simple Objects
The majority of objects fall into the category of "simple objects",
or those which are not exchanged between C++ and the ScriptEase engine, and
do not need special dynamic functionality. In this section we will attempt
to walk you through the integration of a simple C++ object. For this example,
we will be using a simple employee class:
class SimpleEmployee
{
public:
SimpleEmployee( string name, float hourlyWage )
: m_name(name), m_hourlyWage(hourlyWage)
{ }
float calculateWeeklyWages( float hoursWorked )
{ return m_hourlyWage*hoursWorked; }
void printInfo()
{
cout << " Name: " << m_name << endl;
cout << " Hourly Wage: " << m_hourlyWage << endl;
}
private:
string m_name;
float m_hourlyWage;
};
We want the user to be able to create SimpleEmployee objects from the ScriptEase
engine, as well as be able to export C++ objects to the scripting environment.
We want to be able to call the calculateWeeklyWage() and printInfo()
methods from the scripting environment.
The first step is to modify your class to inherit from the base class SEObjectWrapper .
To do this, you must include the header file "jseobject.h "
first, found in the srcapp directory of the SDK installation.
You then must include the "jseobject.cpp " (also found
in srcapp ) into your poject. The method for doing this varies
from platform to platform. Our SimpleEmployee class now looks like this (making
sure to include our jseopt.h file as well):
#include "jseopt.h"
#include "jseobject.h"
class SimpleEmployee : public SEObjectWrapper
{
public:
// ...
The second step is to declare the interface within your class. This declaration
MUST be the first statement within your class, as it defines some important
methods used elsewhere. The macro to declare the interface is SEOBJWRAPPER_DECLARE_INTERFACE() .
It takes a single parameter, which MUST be the same name as the name of the
class in which it is used. Applying this to our sample, we now have:
#include "jseopt.h"
#include "jseobject.h"
class SimpleEmployee : public SEObjectWrapper
{
SEOBJWRAPPER_DECLARE_INTERFACE(SimpleEmployee);
public:
// ...
The next step is to declare methods which we are going to export to the ScriptEase
engine. The first such method is the constructor. You need to define a version
of the constructor which takes a jseContext as the only parameter.
This form of the constructor gets called when the object is created from within
the ScriptEase engine. The rest of the exported methods are declared using
the macro SEOBJWRAPPER_DECLARE_METHOD() . This macro has two parameters,
the name of the method and the return type of the method. The supported return
types are the following: int, long, float, double, string, void, jseVariable,
SEObjectWrapperReference . The first types are self-explanatory. The
final two special types are described in the section "Advanced
Programming Techniques". The second parameter to the macro is the name
of the function you wish to export. This macro will create a wrapper method
declaration of the form:
retType methodName( jseContext );
You will need to define this method and fill in the contents later. Implementing
this macro and the new constructor, our example now includes:
// ...
SimpleEmployee( jseContext );
// ...
SEOBJWRAPPER_DECLARE_METHOD(calculateWeeklyWages,float);
SEOBJWRAPPER_DECLARE_METHOD(printInfo,void);
// ...
That is all that needs to be done within the class definition. Outside of
the class, you need to export any declared methods to the ScriptEase engine.
This is accomplished via a series of macros. For every class that inherits
from SEObjectWrapper, you must define a section enclosed by SEOBJWRAPPER_BEGIN_INTERFACE() and
SEOBJWRAPPER_END_INTERFACE() macros. The first macro takes the
name of the class again as a parameter, while the second macro takes no parameters.
These are macros which enclose the methods to be exported to the ScriptEase
engine. All the methods that you declared within the class must be exported
here with the macro SEOBJWRAPPER_EXPORT_METHOD() . This macro takes
two parameters, the name of the method and the number of arguments it takes.
To allow for any number of arguments, specify -1 as the number of arguments.
For greater control over arguments and function attributes, consult the section
"Advanced Programming Techniques". Our
example now has a corresponding .cpp file that begins like this:
#include "SimpleEmployee.h"
SEOBJWRAPPER_BEGIN_INTERFACE(SimpleEmployee)
SEOBJWRAPPER_EXPORT_METHOD(calculateWeeklyWages,1)
SEOBJWRAPPER_EXPORT_METHOD(printInfo,0)
SEOBJWRAPPER_END_INTERFACE()
Now you must write the methods to map to the existing methods of your class.
This process is simple with the SEOBJWRAPPER_PARAMETER() macro,
which automatically gets ScriptEase parameters and converts them to the appropriate
type. To use this macro, you must first declare the variables in your method.
Then, simply pass the name and type of the variable, as well as the offset within
the argument list (starting at 0) to this macro. The supported types of the
macro are the same as the types supported by the SEOBJWRAPPER_DECLARE_METHOD
macro, except for the void type. The macro returns a boolean value
indicating whether the parameter was present and in the appropriate format.
If this macro fails, you should immediately return from your method. In case
of an error, the return value is ignored, and the SEObjectWrapper class will
propagate that error to the ScriptEase engine. Typically, you will simply want
to convert the arguments into native C++ types and pass them on to the actual
C++ method. This is exactly what we do in our example. The final code for
our sample looks like this:
SimpleEmployee.h
#ifndef _SIMPLE_EMPLOYEE_H_
#define _SIMPLE_EMPLOYEE_H_
#include "jseopt.h"
#include "jseobject.h"
#include <iostream>
class SimpleEmployee : public SEObjectWrapper
{
SEOBJWRAPPER_DECLARE_INTERFACE(SimpleEmployee);
public:
SimpleEmployee( string name, float hourlyWage )
: SEObjectWrapper(), m_name(name), m_hourlyWage(hourlyWage)
{ }
SimpleEmployee( jseContext );
float calculateWeeklyWages( float hoursWorked )
{ return m_hourlyWage*hoursWorked; }
void printInfo()
{
cout << " Name: " << m_name << endl;
cout << " Hourly Wage: " << m_hourlyWage << endl;
}
SEOBJWRAPPER_DECLARE_METHOD(calculateWeeklyWages,float);
SEOBJWRAPPER_DECLARE_METHOD(printInfo,void);
private:
string m_name;
float m_hourlyWage;
};
#endif /* _SIMPLE_EMPLOYEE_H_ */
SimpleEmployee.cpp
#include "SimpleEmployee.h"
SEOBJWRAPPER_BEGIN_INTERFACE(SimpleEmployee)
SEOBJWRAPPER_EXPORT_METHOD(calculateWeeklyWages,1)
SEOBJWRAPPER_EXPORT_METHOD(printInfo,0)
SEOBJWRAPPER_END_INTERFACE()
SimpleEmployee::SimpleEmployee( jseContext jsecontext )
: SEObjectWrapper(jsecontext)
{
if( !SEOBJWRAPPER_PARAMETER(m_name,string,0) )
return;
if( !SEOBJWRAPPER_PARAMETER(m_hourlyWage,float,1) )
return;
}
float
SimpleEmployee::calculateWeeklyWages( jseContext )
{
float hoursWorked;
if( !SEOBJWRAPPER_PARAMETER(hoursWorked,float,0) )
return 0;
return calculateWeeklyWages( hoursWorked );
}
void
SimpleEmployee::printInfo( jseContext )
{
printInfo();
}
That's all the changes that need to made to the class. Before you can use
the class, however, you must initialize the class when we create the root context.
This varies from program to program but wherever you call jseInitializeExternalLink()
be sure to call the init() method of you new class as well These
methods must be called for every object that inherits from the SEObjectWrapperclass.
For most projects, this will look something similar to this:
// ... Create context with jseInitializeExternalLink()...
SimpleEmployee::init( rootContext );
// ... Use the context ...
Now your object is completely integrated with the ScriptEase engine. Writing
these wrapper methods is significantly easier than trying to understand how
to maintain an object in both C++ and script form, and the automatic exporting
of methods and parameter retrieval makes writing wrapper functions easy. With
what's been presented in this section, you should be able to integrate most
simple classes within your program. The next subject covers dynamic objects,
which allow you to do things not possible with the mechanisms that have been
described in this section.
2. Creating Dynamic Objects
ScriptEase provides a number of number of facilities for managing dynamic objects.
These objects can execute methods when members are set or retrieved to provide
greater functionality. The SEDynamicObjectWrapper class provides two ways to
take advantage of the dynamic nature of ScriptEase objects. The first option
is the dynamic sharing of data members, so that executing "foo.hourlyWage = 40"
will set the hourlyWage member of the underlying C++ class. This
ensures that the C++ class and script object always contain the same data at
any point. The other option is to declare methods that get called when a property
is set or retrieved. For our example, we will be using a modified version of
the SimpleEmployee class from the first section, with the addition of the manager
attribute, which translates to a $100 bonus per week. This class looks like
this:
class DynamicEmployee
{
public:
DynamicEmployee( string name, float hourlyWage, bool isManager )
: name(name), hourlyWage(hourlyWage)
{
setManager(isManager);
}
float calculateWeeklyWages( float hoursWorked )
{ return hourlyWage*hoursWorked + m_bonus; }
void printInfo()
{
cout << " Name: " << name;
if( m_isManager )
cout << " (Manager)";
cout << endl << " Hourly Wage: " << hourlyWage << endl;
}
void setManager( bool isManager )
{
m_isManager = isManager;
if( isManager )
m_bonus = 100;
else
m_bonus = 0;
}
string name;
float hourlyWage;
private:
bool m_isManager;
float m_bonus;
};
There are several important things to note about this class. The name
and hourlyWage fields are public members, allowing the user to
change them. These are the properties that we'll wish to expose to the script
engine. The setManager() method performs some extra work when
changing to or from manager status in order to adjust the bonus. While a method
is necessary in C++, from the ScriptEase point of view, it would be significantly
easier to allow direct assignment such as "employee.isManager = true "
and handle the extra code within the dynamic method.
The first thing we need to do is similar to the first example. We include
"jseobject.h" and inherit from SEDynamicObjectWrapper .
The first line of our class must be an interface definition macro, called SEDYNOBJWRAPPER_DECLARE_INTERFACE() .
Because the SEDynamicObjectWrapper class inherits from SEObjectWrapper, all
the methods and macros available to the simple objects are also available to
the dynamic objects. The dynamic version of the interface declaration macro
automatically initializes the needed data, so DO NOT call SEOBJWRAPPER_DECLARE_INTERFACE()
in the same class as calling the dynamic version. Our new class begins like
this:
#include "jseopt.h"
#include "jseobject.h"
class DynamicEmployee : public SEDynamicObjectWrapper
{
SEDYNOBJWRAPPER_DECLARE_INTERFACE(DynamicEmployee);
public:
Since we can use the same simple methods as in the first example, we will export
the printInfo() and calculateWeeklyWages() methods
just as we did before. What we are more concerned with is the integration of
dynamic methods. We will begin by declaring the name and hourlyWage
dynamic values. To do this, we use the SEDYNOBJWRAPPER_DECLARE_VALUE()
macro. This macro takes two parameters, the name of the variable and the type
of the variable. The types supported are the same as those stated in the first
section. The values must first be declared elsewhere in the class.
To make the isManager attribute a dynamic callback, we use the
SEDYNOBJWRAPPER_DECLARE_CALLBACK() macro. This macro takes two
parameters, the name of the callback and the type that it expects. It actually
generates the definitions for two methods, which you must fill in. The methods
it creates are:
retType SEGet_callbackName();
void SEPut_callbackName( retType value );
Where callbackName is replaced by the name that you supply. Simply
define these methods to do what you like within your .cpp file. The header
for our class now looks like this:
// ...
DynamicEmployee( jseContext );
// ...
SEOBJWRAPPER_DECLARE_METHOD(calculateWeeklyWages,float);
SEOBJWRAPPER_DECLARE_METHOD(printInfo,void);
SEDYNOBJWRAPPER_DECLARE_VALUE(name,string);
SEDYNOBJWRAPPER_DECLARE_VALUE(hourlyWage,float);
SEDYNOBJWRAPPER_DECLARE_CALLBACK(isManager,bool);
// ...
We are finished with the header file at this point. Now we must add the necessary
code to export our dynamic properties to the .cpp file. Note that you MUST
still declare a SEOBJWRAPPER_BEGIN_INTERFACE() block even if you
are not exporting any methods. To include dynamic object support, simply create
a SEDYNOBJWRAPPER_BEGIN_INTERFACE() block around the properties
we wish to export. It is required to correctly initialize the object. Within
the interface block, use the macros SEDYNOBJWRAPPER_EXPORT_VALUE()
and SEDYNOBJWRAPPER_EXPORT_CALLBACK() , each which takes the name
of the value or callback. Every value or callback declared in the header file
must be exported in the .cpp file. For our example we have the following interface
definition blocks:
#include "DynamicEmployee.h"
SEOBJWRAPPER_BEGIN_INTERFACE(DynamicEmployee)
SEOBJWRAPPER_EXPORT_METHOD(calculateWeeklyWages,1)
SEOBJWRAPPER_EXPORT_METHOD(printInfo,0)
SEOBJWRAPPER_END_INTERFACE()
SEDYNOBJWRAPPER_BEGIN_INTERFACE(DynamicEmployee)
SEDYNOBJWRAPPER_EXPORT_VALUE(name)
SEDYNOBJWRAPPER_EXPORT_VALUE(hourlyWage)
SEDYNOBJWRAPPER_EXPORT_CALLBACK(isManager)
SEDYNOBJWRAPPER_END_INTERFACE()
The dynamic values are now fnished. However, we still must define the callback
methods, sePut_isManager() and seGet_isManager() .
The code for this is relatively simple, and can be seen in the final version
of our class:
DynamicEmployee.h
#include "jseopt.h"
#include "jseobject.h"
class DynamicEmployee : public SEDynamicObjectWrapper
{
SEDYNOBJWRAPPER_DECLARE_INTERFACE(DynamicEmployee);
public:
DynamicEmployee( string name, float hourlyWage, bool isManager )
: SEDynamicObjectWrapper(), name(name), hourlyWage(hourlyWage)
{
setManager(isManager);
}
DynamicEmployee(jseContext jsecontext);
float calculateWeeklyWages( float hoursWorked )
{ return hourlyWage*hoursWorked + m_bonus; }
void printInfo()
{
cout << " Name: " << name;
if( m_isManager )
cout << " (Manager)";
cout << endl << " Hourly Wage: " << hourlyWage << endl;
}
void setManager( bool isManager )
{
m_isManager = isManager;
if( isManager )
m_bonus = 100;
else
m_bonus = 0;
}
string name;
float hourlyWage;
SEOBJWRAPPER_DECLARE_METHOD(calculateWeeklyWages,float);
SEOBJWRAPPER_DECLARE_METHOD(printInfo,void);
SEDYNOBJWRAPPER_DECLARE_VALUE(name,string);
SEDYNOBJWRAPPER_DECLARE_VALUE(hourlyWage,float);
SEDYNOBJWRAPPER_DECLARE_CALLBACK(isManager,bool);
private:
bool m_isManager;
float m_bonus;
};
DynamicEmployee.cpp
#include "DynamicEmployee.h"
SEOBJWRAPPER_BEGIN_INTERFACE(DynamicEmployee)
SEOBJWRAPPER_EXPORT_METHOD(calculateWeeklyWages,1)
SEOBJWRAPPER_EXPORT_METHOD(printInfo,0)
SEOBJWRAPPER_END_INTERFACE()
SEDYNOBJWRAPPER_BEGIN_INTERFACE(DynamicEmployee)
SEDYNOBJWRAPPER_EXPORT_VALUE(name)
SEDYNOBJWRAPPER_EXPORT_VALUE(hourlyWage)
SEDYNOBJWRAPPER_EXPORT_CALLBACK(isManager)
SEDYNOBJWRAPPER_END_INTERFACE()
DynamicEmployee::DynamicEmployee( jseContext jsecontext)
: SEDynamicObjectWrapper(jsecontext)
{
if( !SEOBJWRAPPER_PARAMETER(name,string,0) )
return;
if( !SEOBJWRAPPER_PARAMETER(hourlyWage,float,1) )
return;
bool isManager;
if( !SEOBJWRAPPER_PARAMETER(isManager,bool,2) )
return;
setManager(isManager);
}
float
DynamicEmployee::calculateWeeklyWages( jseContext )
{
float hoursWorked;
if( !SEOBJWRAPPER_PARAMETER(hoursWorked,float,0) )
return 0;
return calculateWeeklyWages( hoursWorked );
}
void
DynamicEmployee::printInfo( jseContext )
{
printInfo();
}
bool
DynamicEmployee::seGet_isManager()
{
return m_isManager;
}
void
DynamicEmployee::sePut_isManager( bool value )
{
setManager(value);
}
That's all it takes to create effective dynamic C++ objects in ScriptEase.
Remember, though, that you still must call the init() methods on
your class just as we did in the first example. With the two methods of implementing
dynamic objects, you should be able to do nearly anything you wish. If there
is some functionality not provided by these mechanisms, it is probably covered
in the next section
3. Advanced Programming Techniques
What has been demonstrated in the first two sections is by no means the limit
to what you can do with these classes. In this section we will describe several
of the more advanced techniques to interact with the SEObjectWrapper and SEDynamicObjectWrapper
interfaces.
Containment of Other Objects
Many of your objects probably contain other objects. If you have SEObjectWrapper
classes that contain other SEObject classes, you will probably need some way
of exporting this information to the ScriptEase engine. The simplest way to
manage members is through the use of two methods, setMember() and
getMember() .
void setMember( const jsecharptr name, const type &value );
bool getMember( const jsecharptr name, type &value);
At their most basic level, the setMember() and getMember()
methods simply assign the given value to the appropriate member. For setMember ,
the first parameter is the name of the member, and the second member is the
value to assign. For getMember , the first parameter is the name
of the member, and the second parameter is a reference to a variable to fill
in with the value. The getMember() method returns true if the
member was present and of the appropriate type, false otherwise.
The real power of containment comes with the use of the SEObjectWrapperReference
type. This type is really a pointer to an SEObjectWrapper object, but it can
be used in any of the methods such as getMember() , setMember() ,
SEOBJWRAPPER_DECLARE_METHOD() , SEDYNOBJWRAPPER_DECLARE_VALUE() ,
SEDYNOBJWRAPPER_DECLARE_CALLBACK() , and SEOBJWRAPPER_PARAMETER() .
In order to use this type, simply declare an SEObjectWrapperReference variable
and pass it to the appropriate macro or method. Once you have gotten the value,
you must then typecast it to your derived type. For systems that support RTII,
you should use the dynamic_cast<> operator to ensure type
safety. Otherwise, simply typecast to the type you expect, but be warned that
the pointer returned could be a non-SEObjectWrapper, though it is unlikely unless
one of your libraries is also using jseSetObjectData on the same
objects.
For our example, we will modify the DynamicEmployee class to contain a few
more members. The first will be 'originalName' , which serves no
practical purpose but demonstrates how to use setMember() . This
is a string which gets set as a non-dynamic member when the object is created.
If you are using non-dynamic objects, this is the way to add contained members
to you object. We then add two members, 'coworker' and 'boss' ,
both dynamic members defined in different ways. The coworker member
is defined as a dynamic value, which means that it must be stored as an SEObjectWrapperReference
and be typecast when it is actually used. The boss member, however,
uses callbacks to do the typecast automatically, so that the member can be access
directly without any trouble. The code for this example follows:
DynamicEmployee.h
#include "jseopt.h"
#include "jseobject.h"
class DynamicEmployee : public SEDynamicObjectWrapper
{
SEDYNOBJWRAPPER_DECLARE_INTERFACE(DynamicEmployee);
public:
DynamicEmployee( string name, float hourlyWage, bool isManager,
DynamicEmployee * boss = NULL)
: SEDynamicObjectWrapper(), name(name), hourlyWage(hourlyWage),
m_boss(boss), coworker(NULL)
{
setManager(isManager);
}
DynamicEmployee(jseContext jsecontext);
float calculateWeeklyWages( float hoursWorked )
{ return hourlyWage*hoursWorked + m_bonus; }
void printInfo()
{
cout << " Name: " << name;
if( m_isManager )
cout << " (Manager)";
cout << endl << " Hourly Wage: " << hourlyWage << endl;
if( coworker != NULL )
cout << " Coworker: "
<< ((DynamicEmployee *)coworker)->name << endl;
if( m_boss != NULL )
cout << " Boss: " << m_boss->name << endl;
}
void setManager( bool isManager )
{
m_isManager = isManager;
if( isManager )
m_bonus = 100;
else
m_bonus = 0;
}
void setBoss( DynamicEmployee *boss );
virtual bool createVariable( const jsecharptr name = NULL,
jseVariable ownerVar = NULL );
string name;
float hourlyWage;
SEOBJWRAPPER_DECLARE_METHOD(calculateWeeklyWages,float);
SEOBJWRAPPER_DECLARE_METHOD(printInfo,void);
SEDYNOBJWRAPPER_DECLARE_VALUE(name,string);
SEDYNOBJWRAPPER_DECLARE_VALUE(hourlyWage,float);
SEDYNOBJWRAPPER_DECLARE_VALUE(coworker,SEObjectWrapperReference);
SEDYNOBJWRAPPER_DECLARE_CALLBACK(isManager,bool);
SEDYNOBJWRAPPER_DECLARE_CALLBACK(boss,SEObjectWrapperReference);
private:
bool m_isManager;
float m_bonus;
DynamicEmployee * m_boss;
SEObjectWrapperReference coworker;
};
DynamicEmployee.cpp
#include "DynamicEmployee.h"
SEOBJWRAPPER_BEGIN_INTERFACE(DynamicEmployee)
SEOBJWRAPPER_EXPORT_METHOD(calculateWeeklyWages,1)
SEOBJWRAPPER_EXPORT_METHOD(printInfo,0)
SEOBJWRAPPER_END_INTERFACE()
SEDYNOBJWRAPPER_BEGIN_INTERFACE(DynamicEmployee)
SEDYNOBJWRAPPER_EXPORT_VALUE(name)
SEDYNOBJWRAPPER_EXPORT_VALUE(hourlyWage)
SEDYNOBJWRAPPER_EXPORT_VALUE(coworker)
SEDYNOBJWRAPPER_EXPORT_CALLBACK(isManager)
SEDYNOBJWRAPPER_EXPORT_CALLBACK(boss)
SEDYNOBJWRAPPER_END_INTERFACE()
DynamicEmployee::DynamicEmployee( jseContext jsecontext )
: SEDynamicObjectWrapper(jsecontext),
m_boss(NULL), coworker(NULL)
{
if( !SEOBJWRAPPER_PARAMETER(name,string,0) )
return;
if( !SEOBJWRAPPER_PARAMETER(hourlyWage,float,1) )
return;
bool isManager;
if( !SEOBJWRAPPER_PARAMETER(isManager,bool,2) )
return;
setManager(isManager);
}
bool
DynamicEmployee::createVariable( const jsecharptr name,
jseVariable ownerVar )
{
if( !SEDynamicObjectWrapper::createVariable(name,ownerVar) )
return false;
setMember("originalName",this->name);
return true;
}
void
DynamicEmployee::setBoss( DynamicEmployee *boss )
{
m_boss = boss;
if( hasContext() && hasVariable() )
{
if( boss != NULL )
{
if( !boss->hasContext() )
boss->setContext( getContext() );
if( !boss->hasVariable() )
boss->createVariable();
}
}
}
float
DynamicEmployee::calculateWeeklyWages( jseContext )
{
float hoursWorked;
if( !SEOBJWRAPPER_PARAMETER(hoursWorked,float,0) )
return 0;
return calculateWeeklyWages( hoursWorked );
}
void
DynamicEmployee::printInfo( jseContext )
{
printInfo();
}
bool
DynamicEmployee::seGet_isManager()
{
return m_isManager;
}
void
DynamicEmployee::sePut_isManager( bool value )
{
setManager(value);
}
SEObjectWrapperReference
DynamicEmployee::seGet_boss()
{
return (SEObjectWrapperReference) m_boss;
}
There are several important things to note about this example. The first is
the overloaded createVariable() method. We want any newly created
variables to have the '.originalName ' member set correctly, so
we automatically add that to this method to ensure that it is added to all variables,
whether they are created from C++ code or from within ScriptEase. You must
call the superclass createVariable() method before you can do anything
in your own method.
The second note is the setBoss() member function. This method
ensures that the class you are adding has a scriptEase variable associated with
it. The object must have had createVariable() called on it or
it will not work. While it can be assumed that the user will have already created
this variable before passing it to setBoss() , this extra code ensures
that the forgetful user doesn't cause a fatal error.
The final important method is the seGet_boss() and sePut_boss()
methods, which get called when the user changes the boss member of a script
variable. These methods convert the SEObjectWrapperReference into
a native pointer inline, so that the code that uses m_boss does
not need to do this cast. This is preferred method of containment with dynamic
members, because the rest of your doesn't have to change (no explicit casts
are needed).
Advanced Method Declarations
The basic macros cover 95% of the situations that a developer may encounter.
There are times, however, when the developer wants more control over the methods
and properties of the objects. The SEObjectWrapper class provides a number
of mechanisms for greater control, as well as allowing the user to add arbitrary
methods to the engine.
Accesing the raw object - In any method function, you can simply access
the current object by calling getVariable() , or casting the (*this)
variable to a jseVariable. Also, you can get the current executing context
by calling currentContext(), even if you aren't currently in a wrapper function.
Using jseVariable - In all of the macros that require types, one of
the possible types is a jseVariable. This allows you to manipulate and edit
members directly, as well as return arbitrary variables from methods, without
having to rely on the auto-conversion techniques of the SEObjectWrapper class.
For example, if you want to create a member that is an object, but not a SEObjectWrapper
object (so you can't use the SEObjectWrapperReference), you could do the following:
jseVariable member = jseCreateVariable(currentContext(),jseTypeObject);
// ... Do something with the object here ...
setMember( "myObject", member );
jseDestroyVariable(currentContext(),member);
The currentContext() method returns the current executing context based on
the context that this variable was created with. It is important to note when
you use jseVariables, assignments take place you might not expect. Any time
you call setMember , getMember , SEOBJWRAPPER_PARAMETER ,
SEOBJWRAPPER_DECLARE_METHOD , the variable will be COPIED. This
means that if you expect to get a parameter by reference, or if you try and
get a variable and modify it's contents, the original variable will NOT be modified.
Of course if it is object, then any changes you make to the object's members
will still be reflected in the copy, but you cannot change the reference to
the object itself. In order to do this type of manipulation, you will need
to use the currentContext() and getVariable() methods
to manually manipulate the object. This allows you do something like this:
jseVariable member = jseGetMemberEx(*this,getVariable(),jseCreateVar);
// ... Do something with the object here ...
jseDestroyVariable(currentContext(),member);
Note that no explicit setMember() is needed, because we are modifying
the variable itself, instead of a copy as would be the case if we used getMember() .
If you define a method that returns a jseVariable, simply return that variable
from your method. Note however, that it will be returned as a temporary variable
and it will be destroyed when it is done being used.
Advanced Method Exporting - The SEOBJWRAPPER_EXPORT_METHOD()
macro is farily limited in it's use. There is a different version, SEOBJWRAPPER_EXPORT_METHOD_EX(),
which includes all the parameters that can be passed to the function description
table. This means that you can do something similar to this:
SEOBJWRAPPER_BEGIN_INTERFACE(MyObject)
SEOBJWRAPPER_EXPORT_METHOD_EX(myMethod,2,-1,jseDontEnum,jseFuncSecure)
SEOBJWRAPPER_END_INTERFACE()
This declares the method to take a minimum of 2 arguments with no maximum,
as well as declaring it don't enumerate (the default without EX) and secure.
For truly advanced use, the elements in between the begin and end interface
tags is really just a jseFunctionDescription table. This means that you can
use any of the macros that you can normally use in the table. By default, all
methods are declared with JSE_PROTOMETH() , and the methods are
wrapped by a dynamically generated wrapper function by the name of __methodName .
Any lib methods that you declare within this block will be added to the global
object by the same name as your class. For example, if you have a static method
that you wish to make available to the script engine, you might do this:
SEOBJWRAPPER_BEGIN_INTERFACE(MyObject)
JSE_LIBMETHOD("myMethod",myMethod,2,-1,jseDontEnum,jseFuncSecure)
SEOBJWRAPPER_END_INTERFACE()
jseLibFunc(MyObject::myMethod)
{
// .. Do stuff here
}
This would add the method MyObject.myMethod to the script engine.
Any methods that you manually define and export to the ScriptEase engine can
get the this variable as a C++ pointer simply by calling getThisVariable() within
a wrapper function.
Lifetime Management (Control changes)
In most cases, a variable created in the C++ code will be deleted by the C++
code, and a variable created by the ScriptEase engine will also be deleted by
the ScriptEase engine. There are cases, though, when you wish to create a C++
object and pass it to the ScritpEase engine, or where you wish to store a pointer
to a C++ object that was supplied from the ScriptEase engine. You can't rely
on the object's lifetime in either case, which is why the SEObject has a built
in mechanism for deleting the variable at the appropriate time. This is accomplished
through the methods takeControl() and relinquishControl() .
An SEObjectWrapper object is viewed as being "owned" by either the
script engine or C++ code. Whichever is the owner is responsible for deleting
it. The reason that you can't store the pointer to an object created by the
script engine is that the script engine "owns" the variable and could
delete it at any time, which would invalidate the memory at that pointer. The
owner of an object can be determined in C++ by calling the isOwnedByScript()
method.
If an object is owned by the ScriptEase engine, then it will be deleted when
the object is no longer used. If an object is owned by the user's code, there
are two options for deleting the object. The first is to simply call relinquishControl() .
This will turn control over to the script, and the object will be deleted when
there are no more references. The second method is to delete the object yourself.
In this case, the SEObjectWrapper invalidates the object and attempts to delete
references to the object. There may still be references to the object in the
ScriptEase engine, in which case the user will try to interact with the objects
but nothing will happen, because they are no longer attached to any C++ object.
For objects that depend on resources with a defined lifetime, this is the only
option. For simple objects, calling relinquishControl() and letting
the ScriptEase engine delete the object is a better option.
If you wish to manage an object, simply call takeControl() on
the object, and you will assume responsibility for deleting the object. The
object will never be deleted by the script engine. If you no longer want to
manage an object, simply call relinquishControl() , but do not attempt
to interact with the object except when receiving the object from script code
(or fetching it from a script object). If you attempt to delete the object,
it will cause a failed assertion. Make sure you take back control before you
attempt and explicit deletion.
Advanced Dynamic Programming
As stated before, the dynamic macros provided should be enough to satisfy most
cases. There are times, however, where you need more control than these mechanisms
provide. For these cases, there are a number of virtual methods which you can
override to do any type of dynamic object programming. These methods are:
// Returns true if it successfully put the value, false otherwise
bool sePutProperty( jseContext jsecontext, string name,
jseVariable var );
// Returns the specified property, or NULL if it is not found
jseVariable seGetProperty( jseContext jsecontext, string name );
// Returns true if the specified property can be put
bool seCanPut( jseContext jsecontext, string name );
// Returns one of the values HP_HAS, HP_HASNOT, HP_CHECK, or
// HP_DIRECTCHECK
int seHasProperty( jseContext jsecontext, string name );
With these methods you simply intercept any significant values and deal with
them as you feel appropriate. In this way you can have objects that have dynamically
added members (such as a map of values). Simply override the sePutProperty
to put those members into some kind of container. For a description of the
HP_XXX values that seHasProperty() returns, see the
ScriptEase:SDK documentation.
Inheritance
There is no simple mechanism to implement inheritance with the SObjectWrapper
class. With some effort, however, you can implement inheritance with just a
few steps.
The first step is to establish the prototype chain so that methods of the superclass
are available to subclass objects. This involves setting the _prototype property
of the subclass's prototype object to be the prototype of the superclass. Essentially,
this translates to:
Subclass.prototype._prototype = Superclass.prototype;
To do this, modify the function table to include a JSE_VARASSIGN macro to do
this assignment. If we were to have a class YetAnotherEmployee derive from
DynamicEmployee, our interface definition would look like this:
SEOBJWRAPPER_BEGIN_INTERFACE(YetAnotherEmployee)
// ... Normal declarations ...
JSE_VARASSIGN("prototype._prototype","DynamicEmployee.prototype",0),
SEOBJWRAPPER_END_INTERFACE()
Now all the methods defined by DynamicEmployee will be available to this class.
The second step is to override the createVariable() method in your
object to call the superclass version of createVariable() . This
is only necessary if the superclass re-implements this method itself. For our
example we would have to define the method:
bool YetAnotherEmploye::createVariable(const jsecharptr name,
jseVariable ownerVar)
{
if( !DynamicEmployee::createVariable(name,ownerVar) )
return false;
// ... Do our stuff here ...
}
The laststep, which is more complicated, involved propagating dynamic methods
up to superclasses. This applies ONLY to dynamic objects that need to have
their dynamic properties chained. In order to do this, you will need to override
the virtual method sePutProperty() and seGetProperty() as defined
in the previous section and call the putProperty() method of the
superclass. This method is not normally exposed, but it is available for this
express purpose. In this example, we would have the following methods:
bool
YetAnotherEmployee::sePutProperty( jseContext jsecontext, string name,
jseVariable var )
{
// ... Do what we need to here, if anything ...
return DynamicEmployee::putProperty(jsecontext,name,var);
}
jseVariable
YetAnotherEmployee::seGetProperty( jseContext jsecontext, string name)
{
// ... Do what we need to here, if anything ...
return DynamicEmployee::getProperty(jsecontext,name,var);
}
For the other dynamic methods, hasProperty() and canPut() ,
you will need to call the methods seHasProperty() and seCanPut()
directly. The reason that you don't call setGetProperty() and
sePutProperty() directly is that the other methods correctly handle
dynamic values and callbacks. After you do this, inheritance of both methods
and dynamic properties should work propertly. |