Mechanism of native code exposure is more or less finalized.
In general the architecture is a mix of COM’s IUnknown/IDispatch
and Objective-C
message passing approaches.
Basic idea is pretty simple and all about these three structures:
som_asset is a structure that custom native object must be derived from:
struct som_asset_t { som_asset_class_t* isa; };
It is pretty simple – contains only one pointer to this structure:
struct som_asset_class_t { long (*asset_add_ref)(som_asset_t* thing); long (*asset_release)(som_asset_t* thing); long (*asset_get_interface)(som_asset_t* thing, const char* name, void** out); som_passport_t* (*asset_get_passport)(som_asset_t* thing); };
As you see som_asset_class in its turn is a pack of 4 pointers to functions that define life time of the asset.
These functions are trivial, especially for those who knows basics of COM.
The only non-COM thing there is the asset_get_passport function:
som_passport_t* (*asset_get_passport)(som_asset_t* thing);
It returns so called passport of the asset – structure that defines properties and methods of the asset that script (or native code) can consume directly to manipulate the asset:
struct som_passport_t { void* reserved; som_atom_t name; // class name som_item_getter_t item_getter; // var item_val = thing[k]; som_item_setter_t item_setter; // thing[k] = item_val; som_item_next_t item_next; // for(var item in thisThing) const som_property_def_t* properties; size_t n_properties; // virtual property thunks const som_method_def_t* methods; size_t n_methods; // method thunks };
Again, nothing spectacular there – bunch of pointers to functions.
All these structures are not meant to be filled manually – instead they should be generated by host language means.
The asset, C++ wrapper
Sciter SDK provides ready to be used C++ wrapper for assets. Here is real life example of SQLite’s Recordset abstraction:
class Recordset : public sciter::om::asset{ sqlite3_stmt *pst; public: Recordset(sqlite3_stmt *pstatement); ~Recordset(); // function rs.next() : true | false // - advances recordset to the next record. // - returns 'true' if operation is successfull and so record buffer is valid. // If end-of-set reached than RS will be auto-finalized (closed). bool next(); // function rs.close() // - finalizes this thing. bool close(); // function rs.name( colno:int [,which: #logical | #field | #table | #database ] ):string // - returns requested name of the field. sciter::string name(int column_no, sciter::string which); // property // rs.length : int // - reports number of columns in the result set int get_length() const; // rs.valid() : true | false // - reports true if the buffer contains valid row. bool isValid(); // [] accessor implementation // - supported cases: // var cv = rs["columnname"]; - by string // var cv = rs[no]; - by ordinal number bool get_item(sciter::value key_or_index, sciter::value& val); // iterator, handler of for( var v in rs ) calls bool get_next(sciter::value& index, sciter::value& val); SOM_PASSPORT_BEGIN(Recordset) SOM_FUNCS( SOM_FUNC(next), SOM_FUNC(name), SOM_FUNC(isValid) ) SOM_PROPS( SOM_RO_VIRTUAL_PROP(length, get_length) ) SOM_ITEM_GET(get_item) SOM_ITEM_NEXT(get_next) SOM_PASSPORT_END private: sciter::value field_to_value(int n); };
As you see that is pure C++ with two Sciter related parts:
a) Class declaration:
class Recordset : public sciter::om::asset{}
This declares class Recordset
to be derived from sciter::om::asset
– out of the box implementation of reference counted som_asset declaration.
And b), passport declaration:
And SOM_PASSPORT_BEGIN(Recordset) ... SOM_PASSPORT_END
. We define there list of properties and methods that will be seen from script side.
The wrapper just needs their names, rest is C++ template black magic.
TBC.