This section finally describes the implementation details and how modules should us the Lua interface. First, in section we look at types supported by the interface, how objects are passed to Lua code and how Lua tables should be accessed from Notion and modules. In section the methods for exporting functions and how they are called from Lua are explained and in section the method for calling Lua functions is explained.
The following types are supported in passing parameters between the C side of Notion and Lua:
|Identifier character||C type||Description|
|S||const char*||Constant string|
|t||ExtlTab||Reference to Lua table|
|f||ExtlFn||Reference to Lua function.|
The difference between identifiers 's' and 'S' is that constant strings as return values are not free'd by the level 1 call handler (see below) after passing to Lua (lua_pushstring always makes a copy) unlike normal strings. String parameters are always assumed to be the property of the caller and thus implicitly const.
Likewise, if a reference to 't' or 'f' is wished to be stored beyond the lifetime of a function receiving such as an argument, a new reference should be created with extl_ref_table/fn. References can be free'd with extl_unref_table/fn. References gotten as return values with the extl_table_get (how these work should be self-explanatory!) functions are property of the caller and should be unreferenced with the above-mentioned functions when no longer needed. The functions extl_fn/table_none() return the equivalent of NULL.
WObjs are passed to Lua code with WWatch userdatas pointing to them so the objects can be safely deleted although Lua code might still be referencing them. (This is why SWIG or tolua would not have helped in creating the interface: extra wrappers for each function would still have been needed to nicely integrate into Notion's object system. Even in the case that Notion was written in C++ this would be so unless extra bloat adding pointer-like objects were used everywhere instead of pointers.) It may be sometimes necessary check in Lua code that a value known to be an Notion WObj is of certain type. This can be accomplished with obj_is(obj, "typename"). To find the type name for a WObj, use obj_typename(obj).
Exported functions (those available to the extension language) are defined by placing EXTL_EXPORT before the function implementation in the C source. The script mkexports.pl is then used to automatically generate exports.c from the source files if MAKE_EXPORTS=modulename is specified in the Makefile. All pointers with type beginning with a 'W' are assumed to be pointers to something inheriting WObj. In addition to a table of exported functions and second level call handlers for these, exports.c will contain two functions module_register_exports() and module_unregister_exports() that should then be called in module initialisation and deinitialisation code.
You've seen the terms level 1 and 2 call handler mentioned above. The Lua support code uses two so called call handlers to convert and check the types of parameters passed from Lua to C and back to Lua. The first one of these call handlers is the same for all exported functions and indeed lua sees all exported as the same C function (the L1 call handler) but with different upvalues passing a structure describing the actual function and the second level call handler. The L1 call handler checks that the parameters received from Lua match a template given as a string of the identifier characters defined above. If everything checks out ok, the parameters are then put in an array of C unions that can contain anyof these known types and the L2 call handler is called.
The L2 call handler (which is automatically generated by the mkexports.pl script) for each exported function checks that the passed WObjs are of the more refined type required by the function and then calls the actual function. While the WObj checking could be done in the L1 handler too, the L2 call handlers are needed because we may not know how the target platform passes each parameter type to the called function. Thefore we must let the C compiler generate the code to convert from a simple and known enough parameter passing method (the unions) to the actual parameter passing method. When the called function returns everything is done in reverse order for return values (only one return value is supported by the generated L2 call handlers).
The functions extl_call, extl_call_named, extl_dofile and extl_dostring call a referenced function (ExtlFn), named function, execute a string and a file, respectively. The rest of the parameters for all these functions are similar. The 'spec' argument is a string of identifier characters (see above) describing the parameters to be passed. These parameters follow after 'rspec'. For dofile and dostring these parameters are passed in the global table arg (same as used for program command lien parameters) and for functions as you might expect. The parameter 'rspec' is a similar description of return values. Pointers to variables that should be set to the return values follow after the input values. The return value of all these functions tells if the call and parameter passing succeeded or not.
Sometimes it is necessary to block calls to all but a limited set of Notion functions. This can be accomplished with extl_set_safelist. The parameter to this function is a NULL-terminated array of strings and the return value is a similar old safelist. The call extl_set_safelist(NULL) removes any safelist and allows calls to all exported functions.
Configuration files should be read as before with the function read_config_for except that the list of known options is no longer present.
Winprops are now stored in Lua tables and can contain arbitrary properties. The 'proptab' entry in each WClientWin is a reference to a winprop table or extl_table_none() if such does not exist and properties may be read with the extl_table_gets functions. (It is perfectly legal to pass extl_table_none() references to extl_table_get*.)