Languages/Smoke: Difference between revisions

From KDE TechBase
No edit summary
m (Ochurlaud moved page Development/Languages/Smoke to Languages/Smoke)
 
(17 intermediate revisions by 7 users not shown)
Line 1: Line 1:
= Overview =
= 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
Legend has it that SMOKE stands for '''S'''cripting '''M'''eta '''O'''bject '''K'''ompiler '''E'''ngine


Not only does it provide wrappers for every function in every class,
Information about all classes and all methods/functions is stored in
but it also contains meta-information allowing queries of what
cross-referencing tables for fast look-ups. Thereby the whole API of a wrapped library can be used.
functions are available and their arguments and return-types.


All classes, all methods, with all arguments, along with various flags
The main purpose of SMOKE is making it easier to write bindings from
reflecting staticness, virtuality, etc. are stored into
scripting languages to Qt and KDE - with an emphasis on ease of use and flexibility.
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
You can find out more about SMOKE by checking out the git repositories of the
scripting languages to Qt and KDE - with an emphasis on ease of use and flexibility.
[https://projects.kde.org/projects/kde/kdebindings kdebindings module].
 
At the time of writing, the [[Development/Languages/Perl|Perl bindings]], [[Development/Languages/Ruby|Ruby bindings]], [[Development/Languages/Qyoto|C# bindings]] and [[Development/Languages/PHP-Qt|PHP bindings]] are known to use it, you might find valuable usage information there.


You can find out more about SMOKE by checking out the
= Versions =
[http://websvn.kde.org/trunk/KDE/kdebindings/ kdebindings module] of KDE's SVN.


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.
While Smoke works like a charm for Qt3 and Qt4, a modular Smoke2 has been developed which makes it easy to wrap new C++ libraries and plug them together. The result is a noticeable list of technologies available to Smoke-based bindings, as
[http://qt.nokia.com/products/ Qt],
QtUiTools,
[[Projects/WebKit|QtWebkit]],
[[Development/Languages/QtScript|Qtscript]],
[http://www.riverbankcomputing.com/software/qscintilla/intro Qscintilla],
[http://qwt.sourceforge.net/ Qwt],
[[Projects/PIM/Akonadi|Akonadi]],
[[Projects/Plasma|Plasma]],
[[Projects/kdelibs|KDE]],
[[KDevPlatform]],
[[Projects/KHTML|KHtml]],
[[KTextEditor]],
[[Research/Nepomuk|Nepomuk]],
[[Projects/Okular|Okular]],
[[Development/Architecture/KDE4/Phonon|Phonon]],
[[Development/Architecture/KDE4/Solid|Solid]],
[[Soprano]]


= API Documentation =
Info on the Smoke API is available on our [[Development/Languages/Smoke/API_Documentation|API Documentation]] page.


= Example =
= Usage example =
== hello.cpp ==
== hello.cpp ==
<code>
<syntaxhighlight lang="cpp-qt">
#include <smoke.h>
#include <smoke.h>
#include <smoke/qt_smoke.h>
#include <smoke/qtcore_smoke.h>
 
#include <smoke/qtgui_smoke.h>
#include <iostream>
#include <iostream>
#include <string>
#include <string>
#include <stdio.h>
#include <stdio.h>
 
using namespace std;
using namespace std;
 
/*
/*
  * 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
{
{
public:
public:
MySmokeBinding(Smoke *s) : SmokeBinding(s) {}
    MySmokeBinding(Smoke *s) : SmokeBinding(s) {}
void deleted(Smoke::Index classId, void *obj) {
    void deleted(Smoke::Index classId, void *obj) {
printf("~%s (%p)\n", className(classId), obj);
        printf("~%s (%p)\n", className(classId), obj);
}
    }
bool callMethod(Smoke::Index method, void *obj, Smoke::Stack /*args*/, bool /*isAbstract*/) {
    bool callMethod(Smoke::Index method, void *obj,
Smoke::Method meth = smoke->methods[method];
        Smoke::Stack /*args*/, bool /*isAbstract*/)
string name;
    {
        Smoke::Method meth = smoke->methods[method];
// check for method flags
        string name;
if (meth.flags & Smoke::mf_protected) name += "protected ";
if (meth.flags & Smoke::mf_const) name += "const ";
        // check for method flags
        if (meth.flags & Smoke::mf_protected) name += "protected ";
// add the name
        if (meth.flags & Smoke::mf_const) name += "const ";
name += smoke->methodNames[meth.name] + string("(");
        // add the name
// iterate over the argument list and build up the parameter names
        name += smoke->methodNames[meth.name] + string("(");
Smoke::Index *idx = smoke->argumentList + meth.args;
while (*idx) {
        // iterate over the argument list and build up the
name += smoke->types[*idx].name;
        // parameter names
idx++;
        Smoke::Index *idx = smoke->argumentList + meth.args;
if (*idx) name += ", ";
        while (*idx) {
}
            name += smoke->types[*idx].name;
name += ")";
            idx++;
            if (*idx) name += ", ";
if (name == "protected mousePressEvent(QMouseEvent*)")
        }
cout << className(meth.classId) << "(" << obj << ")::" << name << endl;
        name += ")";
return false;
}
        if (name == "protected mousePressEvent(QMouseEvent*)")
            cout << className(meth.classId) << "(" << obj
/*
                << ")::" << name << endl;
* In a bindings runtime, this should return the classname as used in the
        return false;
* bindings language, e.g. Qt::Widget in Ruby or Qyoto.QWidget in C#
    }
*/
char *className(Smoke::Index classId) {
    /*
return (char*) smoke->classes[classId].className;
    * 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
// just for convenience, so we can pass Smoke::ModuleIndexes to std::cout
ostream& operator<<(ostream& lhs, Smoke::ModuleIndex rhs)
ostream& operator<<(ostream& lhs, Smoke::ModuleIndex rhs)
{
{
lhs << "[" << rhs.smoke->moduleName() << ", " << rhs.index << "]";
    lhs << "[" << rhs.smoke->moduleName() << ", " << rhs.index << "]";
return lhs;
    return lhs;
}
}
 
int main(int argc, char **argv)
int main(int argc, char **argv)
{
{
// init the Qt SMOKE runtime
    // init the Qt SMOKE runtime
init_qt_Smoke();
    init_qtcore_Smoke();
    init_qtgui_Smoke();
// create a SmokeBinding for the Qt SMOKE runtime
MySmokeBinding binding(qt_Smoke);
    // create a SmokeBinding for the Qt SMOKE runtime
    MySmokeBinding qtcoreBinding(qtcore_Smoke);
// find the 'QApplication' class
    MySmokeBinding qtguiBinding(qtgui_Smoke);
Smoke::ModuleIndex classId = qt_Smoke->findClass("QApplication");
/* find the methodId. we use a munged method signature, where  
    // find the 'QApplication' class
* $ is a plain scalar
    Smoke::ModuleIndex classId = qtcore_Smoke->findClass("QApplication");
* # is an object
    /* find the methodId. we use a munged method signature, where  
* ? is a non-scalar (reference to array or hash, undef) */
    * $ is a plain scalar
Smoke::ModuleIndex methId = qt_Smoke->findMethod("QApplication", "QApplication$?");  // find the constructor
    * # is an object
cout << "QApplication classId: " << classId << ", QApplication($?) methId: " << methId << endl;
    * ? is a non-scalar (reference to array or hash, undef) */
    Smoke::ModuleIndex methId = classId.smoke->findMethod("QApplication",
Smoke::Class klass = classId.smoke->classes[classId.index];  // get the Smoke::Class
        "QApplication$?");  // find the constructor
    cout << "QApplication classId: " << classId
// findMethod() returns an index into methodMaps, which has information about the classId,  
        << ", QApplication($?) methId: " << methId << endl;
// 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];
    // get the Smoke::Class
    Smoke::Class klass = classId.smoke->classes[classId.index];
Smoke::StackItem stack[3];
   
stack[1].s_voidp = &argc;  // QApplication expects a reference to argc, so we pass it as a pointer
    // findMethod() returns an index into methodMaps, which has
stack[2].s_voidp = argv;
    // information about the classId, methodNameId and methodId. we  
// call the constructor, Smoke::Method::method is the methodId specifically for this class.
    // are interested in the methodId to get a Smoke::Method
(*klass.classFn)(meth.method, 0, stack);
    Smoke::Method meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
// the zeroth element contains the return value, in this case the QApplication instance
    Smoke::StackItem stack[3];
void *qapp = stack[0].s_voidp;
    // QApplication expects a reference to argc, so we pass it as a pointer
    stack[1].s_voidp = &argc;
// method index 0 is always "set smoke binding" - needed for virtual method callbacks etc.
    stack[2].s_voidp = argv;
stack[1].s_voidp = &binding;
    // call the constructor, Smoke::Method::method is the methodId
(*klass.classFn)(0, qapp, stack);
    // specifically for this class.
    (*klass.classFn)(meth.method, 0, stack);
// create a widget
classId = qt_Smoke->findClass("QWidget");
    // the zeroth element contains the return value, in this case the
methId = qt_Smoke->findMethod("QWidget", "QWidget");
    // QApplication instance
cout << "QWidget classId: " << classId << ", QWidget() methId: " << methId << endl;
    void *qapp = stack[0].s_voidp;
klass = classId.smoke->classes[classId.index];
    // method index 0 is always "set smoke binding" - needed for
meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
    // virtual method callbacks etc.
    stack[1].s_voidp = &qtguiBinding;
(*klass.classFn)(meth.method, 0, stack);
    (*klass.classFn)(0, qapp, stack);
void *widget = stack[0].s_voidp;
// set the smoke binding
    // create a widget
stack[1].s_voidp = &binding;
    classId = qtcore_Smoke->findClass("QWidget");
(*klass.classFn)(0, widget, stack);
    methId = classId.smoke->findMethod("QWidget", "QWidget");
    cout << "QWidget classId: " << classId
// show the widget
        << ", QWidget() methId: " << methId << endl;
methId = qt_Smoke->findMethod("QWidget", "show");
cout << "QWidget classId: " << classId << ", show() methId: " << methId << endl;
    klass = classId.smoke->classes[classId.index];
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, 0, stack);
// we don't even need findClass() when we use the classId provided by the MethodMap
    void *widget = stack[0].s_voidp;
methId = qt_Smoke->findMethod("QApplication", "exec");
    // set the smoke binding
cout << "QApplication classId: " << qt_Smoke->methodMaps[methId.index].classId << ", exec() methId: " << methId << endl;
    stack[1].s_voidp = &qtguiBinding;
klass = methId.smoke->classes[methId.smoke->methodMaps[methId.index].classId];
    (*klass.classFn)(0, widget, stack);
meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
(*klass.classFn)(meth.method, 0, stack);  // call QApplication::exec()
    // show the widget
    methId = classId.smoke->findMethod("QWidget", "show");
int retval = stack[0].s_int;  // store the return value of QApplication::exec()
    cout << "QWidget classId: " << classId << ", show() methId: "
        << methId << endl;
// destroy the QApplication instance
    meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
methId = qt_Smoke->findMethod("QApplication", "~QApplication");
    (*klass.classFn)(meth.method, widget, 0);
cout << "QApplication classId: " << qt_Smoke->methodMaps[methId.index].classId << ", ~QApplication() methId: " << methId << endl;
meth = methId.smoke->methods[methId.smoke->methodMaps[methId.index].method];
    // we don't even need findClass() when we use the classId provided
(*klass.classFn)(meth.method, qapp, 0);
    // by the MethodMap
    methId = qtgui_Smoke->findMethod("QApplication", "exec");
// destroy the smoke instance
    cout << "QApplication classId: " << qtgui_Smoke->methodMaps[methId.index].classId
delete qt_Smoke;
        << ", exec() methId: " << methId << endl;
// return the previously stored value
    klass = methId.smoke->classes[methId.smoke->methodMaps[methId.index].classId];
return retval;
    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 = qtgui_Smoke->findMethod("QApplication", "~QApplication");
    cout << "QApplication classId: "
        << qtgui_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 qtcore_Smoke;
    delete qtgui_Smoke;
    // return the previously stored value
    return retval;
}
}
</code>
</syntaxhighlight>


== CMakeLists.txt ==
== CMakeLists.txt ==
Line 174: Line 218:
set(hello_src hello.cpp)
set(hello_src hello.cpp)
add_executable(hello ${hello_src})
add_executable(hello ${hello_src})
target_link_libraries(hello smokeqt)
target_link_libraries(hello smokebase smokeqtcore smokeqtgui)
</pre>
</pre>
This assumes that you've installed KDE4 and kdebindings. If you only have SmokeQt installed, you need to adjust the paths accordingly.
This assumes that you've installed KDE4 and kdebindings. If you only have SmokeQt installed, you need to adjust the paths accordingly.
= How to create a SMOKE lib =
To generate SMOKE libs for your own libs and APIs, a new tool has been written, called 'smokegen'. It's included in kdebindings since KDE SC 4.4. We're still missing a FindSmokegen.cmake file, but it's being worked on. Still, here's a quick tutorial to generate your own smoke lib:
smokegen itself only does the parsing of C++ header files, it then expects a plugin which does something useful with the parsed information. Currently there are two plugins (called a 'generator') available: the 'dump' generator, which simply prints the available classes to stdout, and the 'smoke' generator, which actually generates the Smoke source files.
== Setting up the parser ==
For convenience, the options and switches that smokegen requries are stored in an xml-file. Such config files for Qt-based APIs and KDE-based APIs are already installed with the smokeqt and smokekde libs (in <prefix>/share/smokegen). Give them to smokegen with the -config option, as in
<syntaxhighlight lang="bash">smokegen -config /usr/share/smokegen/kde-config.xml</syntaxhighlight>
You can extend options, like include dirs, by giving additional parameters on the command line. For example, you can give additional include dirs with the -I flag:
<syntaxhighlight lang="bash">smokegen -config /usr/share/smokegen/kde-config.xml -I /usr/include/MyFancyApp</syntaxhighlight>
smokegen --help will show a complete list of possible options. You'll typically only need -I, -dm, -o and sometimes -g (the default generator in the config.xml files is 'smoke', but sometimes you might want to override it with 'dump' to get a list of classes).
smokegen then expects a list of headers after a double dash. Typically we write an 'include-all' header, like Qt does, to simplify matters. It's also the most secure way to conditionally include headers (on different platforms for example) and later get the smoke compilation right. Such an include file could look like this:
<syntaxhighlight lang="cpp-qt">
// the parser doesn't understand the __attribute__ stuff of GCC - disable it for the smokegen run.
#define MYAPP_EXPORT
// Since KDE 4.5 this is also supported (and encouraged):
// #ifdef __SMOKEGEN_RUN__
// #  define MYAPP_EXPORT
// #endif
// This is needed on windows platforms, where MYAPP_EXPORT typically expands
// to __dllimport and is needed for successful compilation.
#include <myheader_a.h>
#include <myheader_b.h>
#include <subdir/myheader_c.h>
</syntaxhighlight>
So our command line now looks like this:
<syntaxhighlight lang="bash">smokegen -config /usr/share/smokegen/kde-config.xml \
-I /usr/include/MyFancyApp -- myapp_includes.h</syntaxhighlight>
== Setting up the SMOKE generator ==
Now we still need a smokeconfig.xml for our 'smoke' generator, so that it knows what our modules is called, which parent modules it has, what classes should be included, etc. For elaborate smokeconfig.xml files you can take a look at the ones for the various smoke libs shipped in the kdebindings module, in the subdirs smoke/<moduleName>/smokeconfig.xml.
A very simple smokeconfig.xml would look like this:
<syntaxhighlight lang="xml">
<config>
    <moduleName>myapp</moduleName>
    <!-- Our classes inherit from classes that are in the kdecore and kdeui modules. -->
    <parentModules>
        <module>kdecore</module>
        <module>kdeui</module>
    </parentModules>
    <!-- how many source files should the generator create? -->
    <parts>10</parts>
    <!-- the following two sections are needed for every Qt based module! -->
    <scalarTypes>
        <!-- QString is a class, but represented as a scalar (#) in munged names -->
        <typeName>QString</typeName>
    </scalarTypes>
    <voidpTypes>
        <!-- both are classes, but they are represented as Smoke::t_voidp -->
        <typeName>QStringList</typeName>
        <typeName>QString</typeName>
    </voidpTypes>
    <!-- regexps for signatures of methods and functions that we don't want
        to be wrapped in the smoke module -->
    <exclude>
        <!-- we don't want these private members in smoke.. -->
        <signature>.*::d_ptr.*</signature>
        <signature>.*::q_ptr.*</signature>
        <!-- we also don't want internalFunction(MyType*) in the smoke lib -->
        <signature>.*internalFunction\(MyType\*\).*</signature>
    </exclude>
    <!-- regexps for signatures of top-level functions we want to have included -->
    <functions>
        <!-- include functions starting with 'myapp' -->
        <name>^myapp.*</name>
        <!-- and operators -->
        <name>.*operator.*</name>
    </functions>
    <classList>
        <class>MyClassA</class>
        <class>MyClassB</class>
        <class>MyNamespaceA::MyClassC</class>
        <class>MyNamespaceA::MyClassC::MyNestedClass</class>
    </classlist>
</config>
</syntaxhighlight>
Again, our revised command line looks like this by now:
<syntaxhighlight lang="bash">smokegen -config /usr/share/smokegen/kde-config.xml -I /usr/include/MyFancyApp \
-smokeconfig smokeconfig.xml -- myapp_includes.h</syntaxhighlight>
And this is it. If you run the command (of course it has to be adjusted to use your own headers and classnames), you should end up with a smokedata.cpp and a bunch of x_*.cpp files which you can compile into one libsmokemyapp.so library.

Latest revision as of 17:39, 10 March 2016

Overview

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

Information about all classes and all methods/functions is stored in cross-referencing tables for fast look-ups. Thereby the whole API of a wrapped library can be used.

The main purpose of SMOKE is making it easier 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 git repositories of the kdebindings module.

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.

Versions

While Smoke works like a charm for Qt3 and Qt4, a modular Smoke2 has been developed which makes it easy to wrap new C++ libraries and plug them together. The result is a noticeable list of technologies available to Smoke-based bindings, as Qt, QtUiTools, QtWebkit, Qtscript, Qscintilla, Qwt, Akonadi, Plasma, KDE, KDevPlatform, KHtml, KTextEditor, Nepomuk, Okular, Phonon, Solid, Soprano

API Documentation

Info on the Smoke API is available on our API Documentation page.

Usage example

hello.cpp

#include <smoke.h>
#include <smoke/qtcore_smoke.h>
#include <smoke/qtgui_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_qtcore_Smoke();
    init_qtgui_Smoke();
 
    // create a SmokeBinding for the Qt SMOKE runtime
    MySmokeBinding qtcoreBinding(qtcore_Smoke);
    MySmokeBinding qtguiBinding(qtgui_Smoke);
 
    // find the 'QApplication' class
    Smoke::ModuleIndex classId = qtcore_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 = classId.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 = &qtguiBinding;
    (*klass.classFn)(0, qapp, stack);
 
    // create a widget
    classId = qtcore_Smoke->findClass("QWidget");
    methId = classId.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 = &qtguiBinding;
    (*klass.classFn)(0, widget, stack);
 
    // show the widget
    methId = classId.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 = qtgui_Smoke->findMethod("QApplication", "exec");
    cout << "QApplication classId: " << qtgui_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 = qtgui_Smoke->findMethod("QApplication", "~QApplication");
    cout << "QApplication classId: "
         << qtgui_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 qtcore_Smoke;
    delete qtgui_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 smokebase smokeqtcore smokeqtgui)

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

How to create a SMOKE lib

To generate SMOKE libs for your own libs and APIs, a new tool has been written, called 'smokegen'. It's included in kdebindings since KDE SC 4.4. We're still missing a FindSmokegen.cmake file, but it's being worked on. Still, here's a quick tutorial to generate your own smoke lib:

smokegen itself only does the parsing of C++ header files, it then expects a plugin which does something useful with the parsed information. Currently there are two plugins (called a 'generator') available: the 'dump' generator, which simply prints the available classes to stdout, and the 'smoke' generator, which actually generates the Smoke source files.

Setting up the parser

For convenience, the options and switches that smokegen requries are stored in an xml-file. Such config files for Qt-based APIs and KDE-based APIs are already installed with the smokeqt and smokekde libs (in <prefix>/share/smokegen). Give them to smokegen with the -config option, as in

smokegen -config /usr/share/smokegen/kde-config.xml

You can extend options, like include dirs, by giving additional parameters on the command line. For example, you can give additional include dirs with the -I flag:

smokegen -config /usr/share/smokegen/kde-config.xml -I /usr/include/MyFancyApp

smokegen --help will show a complete list of possible options. You'll typically only need -I, -dm, -o and sometimes -g (the default generator in the config.xml files is 'smoke', but sometimes you might want to override it with 'dump' to get a list of classes).

smokegen then expects a list of headers after a double dash. Typically we write an 'include-all' header, like Qt does, to simplify matters. It's also the most secure way to conditionally include headers (on different platforms for example) and later get the smoke compilation right. Such an include file could look like this:

// the parser doesn't understand the __attribute__ stuff of GCC - disable it for the smokegen run.
#define MYAPP_EXPORT

// Since KDE 4.5 this is also supported (and encouraged):
// #ifdef __SMOKEGEN_RUN__
// #  define MYAPP_EXPORT
// #endif
// This is needed on windows platforms, where MYAPP_EXPORT typically expands
// to __dllimport and is needed for successful compilation.

#include <myheader_a.h>
#include <myheader_b.h>

#include <subdir/myheader_c.h>

So our command line now looks like this:

smokegen -config /usr/share/smokegen/kde-config.xml \
-I /usr/include/MyFancyApp -- myapp_includes.h

Setting up the SMOKE generator

Now we still need a smokeconfig.xml for our 'smoke' generator, so that it knows what our modules is called, which parent modules it has, what classes should be included, etc. For elaborate smokeconfig.xml files you can take a look at the ones for the various smoke libs shipped in the kdebindings module, in the subdirs smoke/<moduleName>/smokeconfig.xml. A very simple smokeconfig.xml would look like this:

<config>
    <moduleName>myapp</moduleName>

    <!-- Our classes inherit from classes that are in the kdecore and kdeui modules. -->
    <parentModules>
        <module>kdecore</module>
        <module>kdeui</module>
    </parentModules>

    <!-- how many source files should the generator create? -->
    <parts>10</parts>

    <!-- the following two sections are needed for every Qt based module! -->
    <scalarTypes>
        <!-- QString is a class, but represented as a scalar (#) in munged names -->
        <typeName>QString</typeName>
    </scalarTypes>
    <voidpTypes>
        <!-- both are classes, but they are represented as Smoke::t_voidp -->
        <typeName>QStringList</typeName>
        <typeName>QString</typeName>
    </voidpTypes>

    <!-- regexps for signatures of methods and functions that we don't want
         to be wrapped in the smoke module -->
    <exclude>
        <!-- we don't want these private members in smoke.. -->
        <signature>.*::d_ptr.*</signature>
        <signature>.*::q_ptr.*</signature>

        <!-- we also don't want internalFunction(MyType*) in the smoke lib -->
        <signature>.*internalFunction\(MyType\*\).*</signature>
    </exclude>

    <!-- regexps for signatures of top-level functions we want to have included -->
    <functions>
        <!-- include functions starting with 'myapp' -->
        <name>^myapp.*</name>

        <!-- and operators -->
        <name>.*operator.*</name>
    </functions>
    <classList>
        <class>MyClassA</class>
        <class>MyClassB</class>
        <class>MyNamespaceA::MyClassC</class>
        <class>MyNamespaceA::MyClassC::MyNestedClass</class>
    </classlist>
</config>

Again, our revised command line looks like this by now:

smokegen -config /usr/share/smokegen/kde-config.xml -I /usr/include/MyFancyApp \
-smokeconfig smokeconfig.xml -- myapp_includes.h

And this is it. If you run the command (of course it has to be adjusted to use your own headers and classnames), you should end up with a smokedata.cpp and a bunch of x_*.cpp files which you can compile into one libsmokemyapp.so library.