Languages/Smoke: Difference between revisions

From KDE TechBase
No edit summary
No edit summary
Line 34: Line 34:


/*
/*
  * This class will intercept all virtual method calls and will get notified when an
  * This class will intercept all virtual method calls and will get
* instance created by smoke gets destroyed.
* notified when an instance created by smoke gets destroyed.
  */
  */
class MySmokeBinding : public SmokeBinding
class MySmokeBinding : public SmokeBinding
Line 46: Line 46:
}
}
bool callMethod(Smoke::Index method, void *obj, Smoke::Stack /*args*/, bool /*isAbstract*/) {
bool callMethod(Smoke::Index method, void *obj,
Smoke::Stack /*args*/, bool /*isAbstract*/)
{
Smoke::Method meth = smoke->methods[method];
Smoke::Method meth = smoke->methods[method];
string name;
string name;
Line 57: Line 59:
name += smoke->methodNames[meth.name] + string("(");
name += smoke->methodNames[meth.name] + string("(");
// iterate over the argument list and build up the parameter names
// iterate over the argument list and build up the
// parameter names
Smoke::Index *idx = smoke->argumentList + meth.args;
Smoke::Index *idx = smoke->argumentList + meth.args;
while (*idx) {
while (*idx) {
Line 67: Line 70:
if (name == "protected mousePressEvent(QMouseEvent*)")
if (name == "protected mousePressEvent(QMouseEvent*)")
cout << className(meth.classId) << "(" << obj << ")::" << name << endl;
cout << className(meth.classId) << "(" << obj
    << ")::" << name << endl;
return false;
return false;
}
}
/*
/*
* In a bindings runtime, this should return the classname as used in the
* In a bindings runtime, this should return the classname as used
* bindings language, e.g. Qt::Widget in Ruby or Qyoto.QWidget in C#
* in the bindings language, e.g. Qt::Widget in Ruby or
* Qyoto.QWidget in C#
*/
*/
char *className(Smoke::Index classId) {
char *className(Smoke::Index classId) {
Line 101: Line 106:
* # is an object
* # is an object
* ? is a non-scalar (reference to array or hash, undef) */
* ? is a non-scalar (reference to array or hash, undef) */
Smoke::ModuleIndex methId = qt_Smoke->findMethod("QApplication", "QApplication$?");  // find the constructor
Smoke::ModuleIndex methId = qt_Smoke->findMethod("QApplication",
cout << "QApplication classId: " << classId << ", QApplication($?) methId: " << methId << endl;
"QApplication$?");  // find the constructor
cout << "QApplication classId: " << classId
    << ", QApplication($?) methId: " << methId << endl;
Smoke::Class klass = classId.smoke->classes[classId.index]; // get the Smoke::Class
// get the Smoke::Class
Smoke::Class klass = classId.smoke->classes[classId.index];
// findMethod() returns an index into methodMaps, which has information about the classId,  
// findMethod() returns an index into methodMaps, which has
// methodNameId and methodId. we are interested in the methodId to get a Smoke::Method
// information about the classId, methodNameId and methodId. we  
// are interested in the methodId to get a Smoke::Method
Smoke::Method meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
Smoke::Method meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
Smoke::StackItem stack[3];
Smoke::StackItem stack[3];
stack[1].s_voidp = &argc;  // QApplication expects a reference to argc, so we pass it as a pointer
// QApplication expects a reference to argc, so we pass it as a pointer
stack[1].s_voidp = &argc;
stack[2].s_voidp = argv;
stack[2].s_voidp = argv;
// call the constructor, Smoke::Method::method is the methodId specifically for this class.
// call the constructor, Smoke::Method::method is the methodId
// specifically for this class.
(*klass.classFn)(meth.method, 0, stack);
(*klass.classFn)(meth.method, 0, stack);
// the zeroth element contains the return value, in this case the QApplication instance
// the zeroth element contains the return value, in this case the
// QApplication instance
void *qapp = stack[0].s_voidp;
void *qapp = stack[0].s_voidp;
// method index 0 is always "set smoke binding" - needed for virtual method callbacks etc.
// method index 0 is always "set smoke binding" - needed for
// virtual method callbacks etc.
stack[1].s_voidp = &binding;
stack[1].s_voidp = &binding;
(*klass.classFn)(0, qapp, stack);
(*klass.classFn)(0, qapp, stack);
Line 126: Line 139:
classId = qt_Smoke->findClass("QWidget");
classId = qt_Smoke->findClass("QWidget");
methId = qt_Smoke->findMethod("QWidget", "QWidget");
methId = qt_Smoke->findMethod("QWidget", "QWidget");
cout << "QWidget classId: " << classId << ", QWidget() methId: " << methId << endl;
cout << "QWidget classId: " << classId
    << ", QWidget() methId: " << methId << endl;
klass = classId.smoke->classes[classId.index];
klass = classId.smoke->classes[classId.index];
Line 139: Line 153:
// show the widget
// show the widget
methId = qt_Smoke->findMethod("QWidget", "show");
methId = qt_Smoke->findMethod("QWidget", "show");
cout << "QWidget classId: " << classId << ", show() methId: " << methId << endl;
cout << "QWidget classId: " << classId << ", show() methId: "
    << methId << endl;
meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
(*klass.classFn)(meth.method, widget, 0);
(*klass.classFn)(meth.method, widget, 0);
// we don't even need findClass() when we use the classId provided by the MethodMap
// we don't even need findClass() when we use the classId provided
// by the MethodMap
methId = qt_Smoke->findMethod("QApplication", "exec");
methId = qt_Smoke->findMethod("QApplication", "exec");
cout << "QApplication classId: " << qt_Smoke->methodMaps[methId.index].classId << ", exec() methId: " << methId << endl;
cout << "QApplication classId: " << qt_Smoke->methodMaps[methId.index].classId
    << ", exec() methId: " << methId << endl;
klass = methId.smoke->classes[methId.smoke->methodMaps[methId.index].classId];
klass = methId.smoke->classes[methId.smoke->methodMaps[methId.index].classId];
meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
(*klass.classFn)(meth.method, 0, stack);  // call QApplication::exec()
int retval = stack[0].s_int;  // store the return value of QApplication::exec()
// call QApplication::exec()
(*klass.classFn)(meth.method, 0, stack);   
// store the return value of QApplication::exec()
int retval = stack[0].s_int;
// destroy the QApplication instance
// destroy the QApplication instance
methId = qt_Smoke->findMethod("QApplication", "~QApplication");
methId = qt_Smoke->findMethod("QApplication", "~QApplication");
cout << "QApplication classId: " << qt_Smoke->methodMaps[methId.index].classId << ", ~QApplication() methId: " << methId << endl;
cout << "QApplication classId: "
    << qt_Smoke->methodMaps[methId.index].classId
    << ", ~QApplication() methId: " << methId << endl;
meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
(*klass.classFn)(meth.method, qapp, 0);
(*klass.classFn)(meth.method, qapp, 0);

Revision as of 16:00, 28 October 2008

Overview

SMOKE is a introspective wrapper around the Qt and KDE frameworks. Legend has it that SMOKE stands for Scripting Meta Object Kompiler Engine

Not only does it provide wrappers for every function in every class, but it also contains meta-information allowing queries of what functions are available and their arguments and return-types.

All classes, all methods, with all arguments, along with various flags reflecting staticness, virtuality, etc. are stored into cross-referencing arrays for fast lookups. One can thus read (and call) the whole Qt API by simply reading/searching these arrays.

The main purpose of SMOKE is to make it possible to write bindings from scripting languages to Qt and KDE - with an emphasis on ease of use and flexibility.

You can find out more about SMOKE by checking out the kdebindings module of KDE's SVN.

At the time of writing, the Perl bindings, Ruby bindings, C# bindings and PHP bindings are known to use it, you might find valuable usage information there.


Example

hello.cpp

  1. include <smoke.h>
  2. include <smoke/qt_smoke.h>
  1. include <iostream>
  2. include <string>
  3. include <stdio.h>

using namespace std;

/*

* This class will intercept all virtual method calls and will get
* notified when an instance created by smoke gets destroyed.
*/

class MySmokeBinding : public SmokeBinding { public: MySmokeBinding(Smoke *s) : SmokeBinding(s) {}

void deleted(Smoke::Index classId, void *obj) { printf("~%s (%p)\n", className(classId), obj); }

bool callMethod(Smoke::Index method, void *obj, Smoke::Stack /*args*/, bool /*isAbstract*/) { Smoke::Method meth = smoke->methods[method]; string name;

// check for method flags if (meth.flags & Smoke::mf_protected) name += "protected "; if (meth.flags & Smoke::mf_const) name += "const ";

// add the name name += smoke->methodNames[meth.name] + string("(");

// iterate over the argument list and build up the // parameter names Smoke::Index *idx = smoke->argumentList + meth.args; while (*idx) { name += smoke->types[*idx].name; idx++; if (*idx) name += ", "; } name += ")";

if (name == "protected mousePressEvent(QMouseEvent*)") cout << className(meth.classId) << "(" << obj << ")::" << name << endl; return false; }

/* * In a bindings runtime, this should return the classname as used * in the bindings language, e.g. Qt::Widget in Ruby or * Qyoto.QWidget in C# */ char *className(Smoke::Index classId) { return (char*) smoke->classes[classId].className; } };

// just for convenience, so we can pass Smoke::ModuleIndexes to std::cout ostream& operator<<(ostream& lhs, Smoke::ModuleIndex rhs) { lhs << "[" << rhs.smoke->moduleName() << ", " << rhs.index << "]"; return lhs; }

int main(int argc, char **argv) { // init the Qt SMOKE runtime init_qt_Smoke();

// create a SmokeBinding for the Qt SMOKE runtime MySmokeBinding binding(qt_Smoke);

// find the 'QApplication' class Smoke::ModuleIndex classId = qt_Smoke->findClass("QApplication"); /* find the methodId. we use a munged method signature, where * $ is a plain scalar * # is an object * ? is a non-scalar (reference to array or hash, undef) */ Smoke::ModuleIndex methId = qt_Smoke->findMethod("QApplication", "QApplication$?"); // find the constructor cout << "QApplication classId: " << classId << ", QApplication($?) methId: " << methId << endl;

// get the Smoke::Class Smoke::Class klass = classId.smoke->classes[classId.index];

// findMethod() returns an index into methodMaps, which has // information about the classId, methodNameId and methodId. we // are interested in the methodId to get a Smoke::Method Smoke::Method meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];

Smoke::StackItem stack[3]; // QApplication expects a reference to argc, so we pass it as a pointer stack[1].s_voidp = &argc; stack[2].s_voidp = argv; // call the constructor, Smoke::Method::method is the methodId // specifically for this class. (*klass.classFn)(meth.method, 0, stack);

// the zeroth element contains the return value, in this case the // QApplication instance void *qapp = stack[0].s_voidp;

// method index 0 is always "set smoke binding" - needed for // virtual method callbacks etc. stack[1].s_voidp = &binding; (*klass.classFn)(0, qapp, stack);

// create a widget classId = qt_Smoke->findClass("QWidget"); methId = qt_Smoke->findMethod("QWidget", "QWidget"); cout << "QWidget classId: " << classId << ", QWidget() methId: " << methId << endl;

klass = classId.smoke->classes[classId.index]; meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];

(*klass.classFn)(meth.method, 0, stack); void *widget = stack[0].s_voidp; // set the smoke binding stack[1].s_voidp = &binding; (*klass.classFn)(0, widget, stack);

// show the widget methId = qt_Smoke->findMethod("QWidget", "show"); cout << "QWidget classId: " << classId << ", show() methId: " << methId << endl; meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method]; (*klass.classFn)(meth.method, widget, 0);

// we don't even need findClass() when we use the classId provided // by the MethodMap methId = qt_Smoke->findMethod("QApplication", "exec"); cout << "QApplication classId: " << qt_Smoke->methodMaps[methId.index].classId << ", exec() methId: " << methId << endl;

klass = methId.smoke->classes[methId.smoke->methodMaps[methId.index].classId]; meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];

// call QApplication::exec() (*klass.classFn)(meth.method, 0, stack);

// store the return value of QApplication::exec() int retval = stack[0].s_int;

// destroy the QApplication instance methId = qt_Smoke->findMethod("QApplication", "~QApplication"); cout << "QApplication classId: " << qt_Smoke->methodMaps[methId.index].classId << ", ~QApplication() methId: " << methId << endl; meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method]; (*klass.classFn)(meth.method, qapp, 0);

// destroy the smoke instance delete qt_Smoke;

// return the previously stored value return retval; }

CMakeLists.txt

find_package(KDE4 REQUIRED)

include_directories(${KDE4_INCLUDE_DIR})
link_directories(${KDE4_LIB_DIR})

set(hello_src hello.cpp)
add_executable(hello ${hello_src})
target_link_libraries(hello smokeqt)

This assumes that you've installed KDE4 and kdebindings. If you only have SmokeQt installed, you need to adjust the paths accordingly.