Binding Macros - KidneyThief/TinScript1.0 GitHub Wiki

Once you've integrated the TinScript context into your solution, the next step is to expose elements of your solution to allow for scripted access. This includes being able to instantiate classes, call functions and methods, set/get variables and members.

To provide this access easily, there are macros have been provided to make the binding transparent

Table of Contents

Class Registration

In order to instantiate a class from script, and to have access to it's members and methods, there are two steps needed:

  • First, we need to declare the class as a script class, which will set up various registration hooks - all of which, are encapsulated in the macro:
    • DECLARE_SCRIPT_CLASS(<class name>, <parent class>);
      • This macro must be added to the class definition. If the class does not have a parent (more more specifically, if the parent class is not declared a script class), simply use "VOID" as the <parent class>.
      • Note: this macro encapsulates some C++ declarations, the semi-colon is required at the end of the macro.
      • An example is the convenience class CScriptObject, defined in TinScript.cpp. An example using inherited classes is found in Unit Tests. Look for the definitions of classes CBase and CChild.
  • Second, we need to "implement" the class registration. In any (but preferably an associated) source file, the following macros are needed to mark the beginning and the end of the class registration:
    • IMPLEMENT_SCRIPT_CLASS_BEGIN(<class name>, <parent class>)
      • It is critical that the <class name> and <parent class> specified exactly match those used in the DECLARE_SCRIPT_CLASS() used in the class definition.
      • Also note, this macro, and the matching 'end' macro, do *not* end in a semi-colon.
    • IMPLEMENT_SCRIPT_CLASS_END()
      • There are no arguments to this macro - it simply bookends the 'begin'.
There is one other requirement for a class to be valid for registration - the class must provide a public default constructor. This is what is called, when the create keyword is used to instantiate the class. An instance of a class is accessed through the variable type object. For more information, refer to the section on Objects.

Member Registration

Once a class has been registered, we'll want access to members of that class. We use the following macro to register a class member:

  • REGISTER_MEMBER(<class name>, <script name>, <member name>);
    • This macro is placed *between* the IMPLEMENT_SCRIPT_CLASS_BEGIN/END macros above.
    • The <class name> must match the name of the class in the IMPLEMENT_SCRIPT_CLASS_BEGIN() macro.
    • The <script name> is whatever identifier you'd like to use, when referring to this member from script.
      • For example, you might have an "m_Position" member in code, but from script, you'd prefer to refer to this as object.position.
    • The <member name> must match exactly, the name of the identifier specified in code. It is from this member that the type and address offset are determined.
  • Registered members must be of a registered type. In addition to the default types of bool, int, float, const char* (script type string), you can register a member that is a non-const pointer to a registered class (script type object).
    • An example of this is the registration of CBase::objmember, found in Unit Tests.

Method Registration

In addition to members, we'll want script access to class methods. As in the case of members, only methods who's signature is comprised of registered types (both return and argument types) are valid for registration. You'll get a compile error if you try to register a non-valid method.

  • REGISTER_METHOD_P<X>(<class name>, <script name>, <method name>, <return type>, <arg1 type>, <arg2 type>, ...);
    • There are up several uniform registration macros - all identical, except in the number of arguments specified. Each macro ends in a "_P", _P0 for a method with no arguments, _P1 for one argument, etc...
    • After the <return type> is specified, for each argument, the argument type is specified.
    • The macros for registering methods do not have to be between the _BEGIN() and End() macros used for registering classes, as to the member macros.
    • There are examples of class method registration in Unit Tests, for classes CBase and CChild.
    • Currently support for up to 8x arguments is implemented.

Global Variable Registration

Not surprisingly, the registration macros used to register global variables (this includes class static members), looks almost identical to class member registration:

  • REGISTER_GLOBAL_VAR(<script name>, <variable name>);
    • The only difference to the member registration macro, is the absence of specifying a class.
    • The <script name> is how the variable is to be referenced from script.
    • The <variable name> is the actual global variable in code.
    • The same rules apply - only variables of registered types are allowed to be registered.
    • An example can be found in Unit Tests, look for the global variable gUnitTestRegisteredInt.

Global Function Registration

As with global variables, the macros to register global functions are almost identical to class method registration:

  • REGISTER_FUNCTION_P<X>(<script name>, <function name>, <return type>, <arg1 type>, <arg2 type>, ...);
    • The number of arguments in the function signature is specified in the '_P<X>'
    • For each argument, the argument type must be provided.
    • Only functions with signatures comprised of registered types are valid for registration.
    • Several examples of registered functions can be found in Unit Tests, such as:
      • int32 UnitTest_MultiplyBy2(int32 number);
      • CVector3f UnitTest_V3fNormalize(CVector3f v0);

Dead Code Elimination

One of the problems with integrated scripting, is that functions that are never called directly from code, are stripped out of the executable as a compiler optimization. This is because the compiler considers such functions as "unreachable code". This is known as 'Dead Code Elimination', and is obviously a problem if a script calls a registered function, and the function implementation has been removed.

There are two convenience macros provided to prevent 'Dead Code Elimination':

  • DECLARE_FILE(<file name>);
    • Specify whatever string you want for <file name>, - it must be a valid identifier, and it must be placed within the source file containing the registered function implementation.
  • REGISTER_FILE(<file name>);
    • The <file name> identifier must match, and this macro must be placed somewhere in the valid code path in your executable. For example, at the start of int main().
All these macros do is to declare a global variable in the file subject to 'Dead Code Elimination', and then extern and set that variable from the main executable code path. This is likely to be a compiler specific solution - so be aware of it when taking TinScript to other platforms.

The Unit Tests are all examples of functions, none of which are directly called anywhere from the executable. Both unittest.cpp, and from the FLTK Demo, TinFLTKDemo.cpp use these macros to avoid dead code elimination.

⚠️ **GitHub.com Fallback** ⚠️