Script bindings

From Code::Blocks
Revision as of 20:08, 17 January 2007 by MortenMacFly (talk | contribs) (Added article (not yet quite well formatted) on how to add script bindings to C::B)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Code::Blocks already has exposed a very large chunk of its SDK and wxWidgets stuff to scripts. Well, but 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. 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). Using SqPlus, it's easy to bind most classes/functions.

SqPlus::SQClassDef<ClassName>("ScriptClassName") creates a class script binding for the C++ class "ClassName". 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").

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");

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 shouldn't 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 (eg. 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.

One important thing to note: 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.

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).

sc_progress.cpp is the simplest and easiest to read basically. It binds only one method without parameters (and the ctor, of course). Things get more complex only for two situations:

1) constructors with arguments, and 2) overloaded functions

Now we are looking at e.g. SqPlus::SQClassDef<EditPathDlg>("EditPathDlg").[...]. It might look complex but it's really not. staticFuncVarArgs(&EditPathDlg_Ctor, "constructor", "*"): This is what defines the custom ctor. Custom ctors must be bound to a squirrel callback which have this signature: SQInteger FuncName(HSQUIRRELVM v). That's SQInteger EditPathDlg_Ctor(HSQUIRRELVM v).

The "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 it was 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, there always "*" is used. Because, for example, "string" is not the same as wxString that is constantly used. Anyway, it's not being used.

In the C function that will act as the constructor all to do now 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 only the actual parameters are to care about. "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.

One thing left 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 biolerplate function, nothing to it, really.

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.

One more thing to explain: "Function overloads".

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" mus 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's to do:

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

All these 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".

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.