Script bindings

From CodeBlocks
Jump to: navigation, search

Code::Blocks already has exposed a very large chunk of its SDK and wxWidgets stuff to scripts (see Scripting commands). How is the scripting binding done in C::B? What is required to do to add additional script binding of parts not (yet) exposed? This page is intended to clarify this question and help developers to add additional script bindings to C::B at source-code level. Using SqPlus, it's easy to bind most classes/functions. Readers might hopefully pick this up pretty fast.

Basically, a look at the sdk/scripting/bindings/sc_*.cpp files will give a good overview (starting with the smaller files). sc_progress.cpp is the simplest and easiest to read basically. It binds only one method without parameters (and the ctor, of course). It is used in several of the examples below.

Bindings are kept separated in logical units as: sc_consts, sc_globals, sc_wxtypes, etc. So before binding something, think carefully where it belongs to (or needs a new unit).

All the sc_* files have a "Register_XXX()" function that actually does the bindings. If a new sc_* file is created, this "Register" function has to be declared as "extern" in scriptbindings.cpp and also needs to be called from there too. Looking at the other "Register_" functions in scriptbindings.cpp should make that clear. So to e.g. use sc_util_dialogs.cpp, it has to be declared as "extern void" in scriptbindings.cpp and called in "RegisterBindings".


Adding a class

To create a script binding for the C++ class "ClassName" you need to call

SqPlus::SQClassDef<ClassName>("ScriptClassName") 
Usually the same name for scripts is used as for the C++ side so "ClassName" in C++ is also bound as "ClassName" in scripts, e.g.
SqPlus::SQClassDef<wxString>("wxString")

For a class to work correctly within a script it must also be declared in sc_base_types.h, using the format:

DECLARE_INSTANCE_TYPE(ClassName)

If you don't do this, any functions that are meant to return on object of your new class will be of type "USERPOINTER" rather than "INSTANCE", and squirrel won't know that the class members belong to it. Hence in the script console you might see errors of the form

 AN ERROR HAS OCCURED [the index 'ClassMember' does not exist]
 CALLSTACK
 *FUNCTION [GetModuleMenu()] C:\codeblocks\share\CodeBlocks\scripts\myscript.script line [35]
 LOCALS
 [myobject] USERPOINTER
 [data] INSTANCE
 [who] 2
 [this] INSTANCE

Here the local variable "myobject" in the plugin script "myscript.script" was supposed to be an object, and its method "ClassMember" was called. However, the object type was not known (see the "[myobject] USERPOINTER" line in the list of locals - it should have been "[myobject] INSTANCE"), and so the call to myobject.ClassMember() was not recognized.

Class members

To bind member functions/variables the class methods of the SqClassDef class is used. The easiest of all is "func" which binds a member function, e.g.,

SqPlus::SQClassDef<ProgressDialog>("ProgressDialog").
emptyCtor().
func(&ProgressDialog::Update, "Update");

As you can see, you can chain these methods together.

The emptyCtor() is a C::B patch on SqPlus to manually add an empty (script) constructor for the object. Without this, this type of object can't be created in scripts. It would only be possible to get it as a return value of a function call or something. The func member binds the ProgressDialog::Update member function as "Update" member function on the script object. So, it's just a one-on-one binding and that's the easiest binding case.

NOTE: Not all bindings should have an emptyCtor(). For example, scripts should not have the ability to create, say, a new "cbProject". Instead, it should go through the standard route: GetProjectManager().NewProject(). Another thing to note about emptyCtor() is that it imposes certain requirements on the C++ class, like that your C++ object must have a public copy constructor (but this is a requirement anyway). Look for an example in all basic classes (e.g., cbEditor). Copy constructors have been added to them which do nothing, just throw an error. They are not used by scripting (SqPlus has been patched for this) but they must exist nevertheless. Anyway, these are some "advanced" scripting issues which will become clearer once the basics are more clear. All of the wxWidgets GUI controls have private copy-ctors btw.

Function arguments

In order to be able to use a registered type as a function's argument, this type must be declared in sc_base_types.h. It's pretty easy, just a macro. If the function to register will not be used as a function's parameter (or return value), then there is no need for that. So it's the DECLARE_INSTANCE_TYPE and DECLARE_ENUM_TYPE macro, depending on what shall be registered.

Constructors with arguments

This next example comes from sc_util_dialogs.cpp:

SqPlus::SQClassDef<EditPathDlg>("EditPathDlg").
    staticFuncVarArgs(&EditPathDlg_Ctor, "constructor", "*").
[...]

The second line might look complex but it's really not. "staticFuncVarArgs" binds a squirrel C function to script (with variable arguments, as the name suggests). The first parameter is the C function name, the second is the function's name in scripts. For constructors it is always "constructor". The third parameter is a string saying what parameters this function accepts. "*" means anything and is used with "staticFuncVarArgs".

NOTE: If, instead of "*", the parameter string had been something like "nnsn", it would mean this function takes ([n]umber, [n]umber, [s]tring, [n]umber) parameters. Clever and with automatic type matching (means if types do not match a script exception is raised). In C::B that form isn't used - "*" is always used. Because, for example, "string" is not the same as wxString that is constantly used. Anyway, it's not being used.

In the example staticFuncVarArgs(&EditPathDlg_Ctor, "constructor", "*") defines the custom ctor. Custom ctors must be bound to a squirrel callback which have the signature SQInteger FuncName(HSQUIRRELVM v), so in this case the callback is SQInteger EditPathDlg_Ctor(HSQUIRRELVM v).

In the C function that will act as the constructor all that needs to be done is to access the parameters and construct the object manually, using SqPlus methods to do it. When a script calls a function and it lands in a C function (like the one used above) it passes all arguments onto the stack (including "this" object, in case of global functions, "this" is the VM). Anyway, to read a function parameter squirrel gives the value at the appropriate place on the stack. Think of it as an array of parameters. So to read parameter three, ask for the value on the fourth place on the stack (remember that parameter 1 is "this", so the second function parameter is at index 3). The "0" as first parameter in some functions is the NULL pointer for the wxWidgets parent (if required). "this" is never used because "this" is handled by SqPlus to call the member function on the correct object.

Usually we only care about the actual parameters. "StackHandler" is a helper class to make it easy to access the stack. It has functions to get/set values on the stack like: "GetInt()", "GetBool()", etc. They all take the index as parameter. You can see this being used in SQInteger EditPairDlg_Ctor(HSQUIRRELVM v) within sc_util_dialogs.cpp.

Also worth mentioning in the same function is this construct:

*SqPlus::GetInstance<wxString>(v, 2)

It gets access to an object address, not a primitive type. This actually returns the wxString object at stack index 2 because StackHandler is not aware of any types that is self-bound. It can't use it so the SqPlus::GetInstance helper function has to be used. The template parameter tells what type is there. The first parameter is the squirrel VM and the last parameter is the stack index. SqPlus::GetInstance always returns a pointer to the object, so it's dereferenced using (*) to access the actual object.

All what's left for this section is SqPlus::PostConstruct. This is done to inform SqPlus about the new object. This will actually bind, e.g., the new EditPathDlg object that has been created to the script. Without it, just the object would be created in the C++ side. But a script is expecting to access that object - that function does that. It is provided with the dtor method because when Squirrel decides that this object needs to be garbage-collected, it calls a callback function to do this. It's a boilerplate function, nothing to it, really.

Other bindings

The "staticFunc" is the same but a "static C function". There is "var()" left but I guess this is clear by now: It's for public member variables.


Overloaded Functions

All "SqClassDef" member functions (like "func") are actually template functions of style func(&foo:bar, "bar"). Suppose void foo::bar(int) and void foo::bar(float) are two overloads of the same function in C++ side. Trying to use the func(&foo:bar, "bar") binding will fail miserably because the compiler doesn't know which one to bind. In that case, "func" must be told which overload is the one to be used. This is done using a typedef. Assuming the usage of the (float) overload, here is what you do:

typedef void(*FOOBAR_FLOAT)(float);
func<FOOBAR_FLOAT>(&foo::bar, "bar");

Final words

Just remember one thing: Strive for simplicity! If you start on wx bindings, there is no need for every enum or function param that is used. Only add what's really required... not everything. This means e.g. for the wx control ctors, just what is necessary, as a matter of fact, no constructor for wx controls.

Links

Sq Plus page on Squirrel wiki

Sq Plus FAQ

Scripting commands lists the current script bindings to date.