Languages/Smoke: Difference between revisions

    From KDE TechBase
    (adding PHP)
    (add a SmokeQt example)
    Line 1: Line 1:
    = Overview =
    '''SMOKE''' is a introspective wrapper around the Qt and KDE frameworks.
    '''SMOKE''' is a introspective wrapper around the Qt and KDE frameworks.
    The legend says it stands for '''S'''cripting '''M'''eta '''O'''bject '''K'''ompiler '''E'''ngine
    The legend says it stands for '''S'''cripting '''M'''eta '''O'''bject '''K'''ompiler '''E'''ngine
    Line 18: Line 19:


    At the time of writing, the [[Development/Languages/Perl|Perl bindings]], [[Development/Languages/Ruby|Ruby bindings]], [[Development/Languages/QtSharp|C# bindings]]  and [[Development/Languages/PHP-Qt|PHP bindings]] are known to use it, you might find valuable usage information there.
    At the time of writing, the [[Development/Languages/Perl|Perl bindings]], [[Development/Languages/Ruby|Ruby bindings]], [[Development/Languages/QtSharp|C# bindings]]  and [[Development/Languages/PHP-Qt|PHP bindings]] are known to use it, you might find valuable usage information there.
    = Example =
    == hello.cpp ==
    <pre>
    #include <smoke.h>
    #include <smoke/qt_smoke.h>
    #include <iostream>
    #include <string>
    #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;
    Smoke::Class klass = classId.smoke->classes[classId.index];  // get the Smoke::Class
    // 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];
    stack[1].s_voidp = &argc;  // QApplication expects a reference to argc, so we pass it as a pointer
    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];
    (*klass.classFn)(meth.method, 0, stack);  // call QApplication::exec()
    int retval = stack[0].s_int;  // store the return value of QApplication::exec()
    // 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;
    }
    </pre>
    == CMakeLists.txt ==
    <pre>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)
    </pre>
    This assumes that you've installed KDE4 and kdebindings. If you only have SmokeQt installed, you need to adjust the paths accordingly.

    Revision as of 15:33, 28 October 2008

    Overview

    SMOKE is a introspective wrapper around the Qt and KDE frameworks. The legend says it 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

    #include <smoke.h>
    #include <smoke/qt_smoke.h>
    
    #include <iostream>
    #include <string>
    #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;
    	
    	Smoke::Class klass = classId.smoke->classes[classId.index];  // get the Smoke::Class
    	
    	// 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];
    	stack[1].s_voidp = &argc;  // QApplication expects a reference to argc, so we pass it as a pointer
    	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];
    	(*klass.classFn)(meth.method, 0, stack);  // call QApplication::exec()
    	
    	int retval = stack[0].s_int;  // store the return value of QApplication::exec()
    	
    	// 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.