Nombas > ScriptEase ISDK DevSpace > Manuals

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.

Home | Scripting | Products | Purchase | Download | Support | Company

Copyright ©2001, Nombas, Inc. All Rights Reserved.
Questions? Visit
http://support.nombas.com/