tag:blogger.com,1999:blog-112641582007-10-18T19:14:14.706-07:00jqlAshnoreply@blogger.comBlogger5125tag:blogger.com,1999:blog-11264158.post-1154146355361316372006-07-28T21:05:00.000-07:002006-07-29T21:11:34.986-07:00Jambi means ProgressI've been <a href="http://jahqueel.blogspot.com/2006/04/intrometaspection.html">waiting</a> a <em>long</em> time for <a href="http://www.trolltech.com/developer/downloads/qt/qtjambi-techpreview">this shoe</a> to drop. It's time to publish some <a href="http://doc.trolltech.com/qtjambi-1.0/qtjambi-generator.html">details</a> <i>[corrected URL]</i> and/or code, <a href="http://blogs.qtdeveloper.net/archives/2006/07/28/qt-jambi-preview-released/">gunnar</a>. Inquiring minds want to <s>steal</s> know your binding technique and use it for the empowerment of KDE.Ashnoreply@blogger.comtag:blogger.com,1999:blog-11264158.post-1146440765691369412006-04-30T15:48:00.000-07:002006-04-30T16:46:05.706-07:00IntrometaspectionSo, how is KDE4 going to understand its own API? It's now May of '06, and the most promising projects I was keeping an eye on haven't been publishing much.<br /><br /><h3>Java Bindings</h3><br /><br />aseigo <a href="http://aseigo.blogspot.com/2005/10/troll-tech-dev-days-05-in-san-jose.html">leaked </a> word of Trolltech working on Java bindings for Qt &mdash; based off the KDevelop parser, IIRC. It was supposed to be available in Q1 '06, but nary a whisper of it now. It wasn't ever really official, but I was looking forward to everyone having something standard to <span style="text-decoration: line-through">steal</span> base KDE bindings on. I hope it's not dead (or castrated like QSA).<br /><br /><h3>KJSEmbed</h3><br /><br />The presumed <i>standard</i> language binding. Is it intended to be a full Qt binding? Or something more practical, for use in KDE applets and such? I have no clue what state it's in right now. I'll cross my fingers.<br /><br /><h3>The Introspection Problem</h3><br /><br />It is <i>really hard</i> to get a detailed API listing for Qt (let alone KDE) to use in a program. This has serious consequences for KDE, the best known of which is difficulty with language bindings. The lack of an API listing leads to <a href="http://www.trolltech.com/products/qsa/index.html" title="QSA">half-measures</a> and <a href="http://www.riverbankcomputing.co.uk/pyqt/" title="PyQt">ugly workarounds</a>. <a href="http://www.kdedevelopers.org/blog/89">Richard</a> is successful with his cluster of language bindings because he knows how to tickle the data out of Kalyptus. I just wish it was easier.Ashnoreply@blogger.comtag:blogger.com,1999:blog-11264158.post-1119069179691762172005-06-17T19:37:00.000-07:002005-06-17T21:32:59.936-07:00Bindings: XPath is my friendI've had good success with <a href="http://search.cpan.org/~awin/GCC-TranslationUnit-1.00/TranslationUnit.pm">my <code>gcc -fdump-translation-unit</code> parser</a> generating XML, and then using <a href="http://www.w3.org/TR/xslt">XSLT</a> to convert the XML into C++ source-code. I expect to create an XSL stylesheet for the C++/moc4 Smoke binding, another one for the O'Caml binding, and yet another one for the C# binding. All using the same raw data generated from GCC. <a href="#example">Scroll down</a> to see an example of what that XML contains.<br /><br />This data replicates what was availble through the Smoke bindings. Instead of trying to shoehorn all that data into giant, esoteric data structures in a monolithic library (which works great -- don't get me wrong), we're going to leave it as XML. Each class which has a Smoke binding will live in its own .so file, and should be dynamically loaded upon request. Inside that file will be the full XML data which describes that class. Upon loading the library, the class XML will be inserted to a global Smoke DOM, which will be the basis of the Smoke automation system.<br /><br />Once we have the DOM loaded, we can preform the same searches Smoke uses, but with XPath.<br /><br />To get a list of all the methods in QPushButton:<br /><br /><code>//type[@name='QPushButton']/struct/method</code><br /><br />To get a list of the callable methods:<br /><br /><code>//type[@name='QPushButton']/struct/method[not(@access='private')]</code><br /><br />To get a list of virtual methods defined:<br /><br /><code>//type[@name='QPushButton']/struct/method[virtual]</code><br /><br />The list of static methods we can call:<br /><br /><code>//type[@name='QPushButton']/struct/function[not(@access='private')]</code><br /><br />The list of Whatever::setFoo() methods which accept 2 arguments:<br /><br /><code>//method[@name='Whatever::setFoo' and (param/last() = 2 or param[3]/default)]</code><br /><br />The Smoke method lookup will be XPath-based. You will need to lookup the XML node corresponding to the function if you want to call it via Smoke. Once you have the node, you will know the types of all the arguments and anything else you could possibly want to know about the method, its class, etc. You can check if a method exists in a class with a virtual destructor by looking up <code>../destructor/virtual</code>. It's far more powerful than Smoke.<br /><br />How about speed? Well, I don't expect this will come anywhere close to C++ speed. However, with smart caching of the XPath query results, the runtime penalty during tight loops can be minimized.<br /><br />For static languages which require compiled bindings, the XPath gives way to XSLT, so there's no significant runtime penalty there. The Smoke library will still load the DOM, and the function calls will still be routed through the meta-calling convention, but that's not so bad.<br /><br /><a name="example"></a>As promised, I'll spam you with XML:<br /><pre><br />&lt;lib&gt;<br /> &lt;type name="QWorkspace" id="6" href="/opt/qt4/include/QtGui/qworkspace.h#42"&gt;<br /> &lt;struct&gt;<br /> &lt;base class="QWidget" access="public"/&gt;<br /> &lt;constructor name="QWorkspace::QWorkspace" access="public"&gt;<br /> &lt;return&gt;<br /> &lt;type name="void"/&gt;<br /> &lt;/return&gt;<br /> &lt;this&gt;<br /> &lt;pointer&gt;<br /> &lt;type name="QWorkspace"/&gt;<br /> &lt;/pointer&gt;<br /> &lt;/this&gt;<br /> &lt;param name="parent"&gt;<br /> &lt;default/&gt;<br /> &lt;pointer&gt;<br /> &lt;type name="QWidget"/&gt;<br /> &lt;/pointer&gt;<br /> &lt;/param&gt;<br /> &lt;/constructor&gt;<br /> &lt;method name="QWorkspace::addWindow" access="public"&gt;<br /> &lt;return&gt;<br /> &lt;pointer&gt;<br /> &lt;type name="QWidget"/&gt;<br /> &lt;/pointer&gt;<br /> &lt;/return&gt;<br /> &lt;this&gt;<br /> &lt;pointer&gt;<br /> &lt;type name="QWorkspace"/&gt;<br /> &lt;/pointer&gt;<br /> &lt;/this&gt;<br /> &lt;param name="w"&gt;<br /> &lt;pointer&gt;<br /> &lt;type name="QWidget"/&gt;<br /> &lt;/pointer&gt;<br /> &lt;/param&gt;<br /> &lt;param name="flags"&gt;<br /> &lt;default/&gt;<br /> &lt;type name="QFlags&amp;lt;Qt::WindowType&amp;gt;"/&gt;<br /> &lt;/param&gt;<br /> &lt;/method&gt;<br /> &lt;method name="QWorkspace::sizeHint" access="public"&gt;<br /> &lt;return&gt;<br /> &lt;type name="QSize"/&gt;<br /> &lt;/return&gt;<br /> &lt;this&gt;<br /> &lt;pointer&gt;<br /> &lt;type name="QWorkspace" qualifier="const"/&gt;<br /> &lt;/pointer&gt;<br /> &lt;/this&gt;<br /> &lt;virtual/&gt;<br /> &lt;/method&gt;<br /> &lt;method name="QWorkspace::paintEvent" access="protected"&gt;<br /> &lt;return&gt;<br /> &lt;type name="void"/&gt;<br /> &lt;/return&gt;<br /> &lt;this&gt;<br /> &lt;pointer&gt;<br /> &lt;type name="QWorkspace"/&gt;<br /> &lt;/pointer&gt;<br /> &lt;/this&gt;<br /> &lt;param name="e"&gt;<br /> &lt;pointer&gt;<br /> &lt;type name="QPaintEvent"/&gt;<br /> &lt;/pointer&gt;<br /> &lt;/param&gt;<br /> &lt;virtual/&gt;<br /> &lt;/method&gt;<br /> &lt;/struct&gt;<br /> &lt;/type&gt;<br />&lt;/lib&gt;<br /></pre><br /><br />If you run that through my crufty <a href="http://perlqt.sf.net/xml/deparse.xsl">deparser XSL</a>, you end up with something that looks like a C++ header. If you have an XSLT-happy browser (aka. Mozilla/* or IE6), you can <a href="http://perlqt.sf.net/xml/example.xml">perform the transformation in your browser</a>, or you can view <a href="http://perlqt.sf.net/xml/example.txt">the finished result, care of libxslt</a>.Ashnoreply@blogger.comtag:blogger.com,1999:blog-11264158.post-1118540633712362202005-06-11T18:23:00.000-07:002005-06-11T18:49:05.883-07:00Smoke : KDE :: COM : Win32<a href="http://www.kdedevelopers.org/node/view/1150">Adam points out</a> the deficiencies of Smoke as a language binding. On the whole, I have to agree with him. Smoke doesn't match what we should recognize as a real language binding.<br /><h3>Smoke is an automation system</h3><br />Similar to COM in Windows, Smoke is an in-process automation system. Distributed Smoke (aka. DSmoke) would be a relatively simple add-on to the built-in marshalling system. The Perl/Ruby Qt interfaces are to the Smoke automation system rather than Qt/KDE themselves. It's analagous to a Win32 interface which only used CreateObject() and COM.<br /><br />Adam goes over a list of what could be gained from having the ability to create real binding for KDE. However, it would likely require maintaining compiler support, and wouldn't be entirely portable to MacOS/Windows. I think Smoke is a necessary evil, while a real set of language bindings should be considered <em>optional</em>.<br /><br />In short, <a href="http://www.jwz.org/doc/worse-is-better.html">worse is better</a>.Ashnoreply@blogger.comtag:blogger.com,1999:blog-11264158.post-1118379826038971702005-06-10T00:33:00.000-07:002005-06-10T02:28:25.250-07:00Smoke for KDE4I've seen mumblings from the KDE folk about increasing their binding support in KDE4. Specifically, <a href="http://aseigo.blogspot.com/">aseigo</a> has <a href="http://aseigo.blogspot.com/2005/06/plasma.html">mentioned</a> making KJS and Python bindings standard.<br /><br />My recommendation is to base the bindings on Qt4's <a href="http://doc.trolltech.com/4.0/qmetaobject.html">metaobject</a> system. The idea would be to adopt the Qt4 meta-calling convention for the next version of Smoke.<br /><h3>How would the Qt4 Smoke bindings work?</h3><br />The goal would be for every function in every class to be available through <a href="http://doc.trolltech.com/4.0/qmetaobject.html#invokeMethod">QMetaObject::invokeMethod()</a> Like Smoke does now, it would allow AUTOLOAD/missing_method based language bindings.<br />However, the metaobject system only gets us 50% of the way there. There are some obstacles to overcome:<br /><ol> <li>There must be a way to call static functions -- like constructors.</li> <li>Not every Qt class inherits from QObject. We need to interface those as well</li> <li>Virtual functions!<br /></li> </ol> I haven't worked out how to solve these issues entirely, yet. Here's where my thoughts have lead me so far.<br /><h3>Static functions</h3><br />The answer here is probably to generate a factory class which is aware of the static functions.<br />Lets try writing Hello World..<br /><pre><br />// Lets say the smoke4 bindings export factory() functions for each class<br />extern "C" void* qapplication_factory();<br />extern "C" void* qpushbutton_factory();<br />extern "C" void* qstring_factory();<br /><br />// public call interface of smoke<br />extern "C" void metacall_wrapper(void* object, int index, void** args) {<br /> // call qt_metacall()<br /> QObject *o = static_cast&lt;QObject*>(object);<br /> o->qt_metacall(QMetaObject::InvokeMetaMember, index, args);<br />}<br /><br />extern "C" int indexOfMember_wrapper(void* object, const char* member) {<br /> // indexOfMember() looks up the method name in the metaobject<br /> QObject *o = static_cast&lt;QObject*>(object);<br /> return o->metaObject()->indexOfMember(member);<br />}<br /><br />extern "C" void* qt_metacast_wrapper(void* object, const char* className) {<br /> QObject *o = static_cast&lt;QObject*>(object);<br /> return o->qt_metacast(className);<br />}<br /><br />extern "C" void smoke_call(void* object, const char* member, void** args) {<br /> // helper function to do everything at once<br /> int _i = indexOfMember_wrapper(object, member);<br /> qt_metacall_wrapper(object, _i, args);<br />}<br /><br />int main(int argc, char **argv) {<br /> // lets assume these *factory() functions are coming from smoke<br /> void *appfactory = qapplication_factory();<br /> void *pbfactory = qpushbutton_factory();<br /> void *strfactory = qstring_factory();<br /><br /> void *app;<br /> void *_a0[] = { &app, &argc, &argv };<br /> smoke_call(appfactory, "new(int,char**)", _a0);<br /></qobject></qobject></qobject></pre><br />At this point, the <code>app</code> variable contains the return-value from <code>new()</code>. In order for us to be able to call <code>qt_metacall()</code> on that variable, we need to mandate that the return-value from <code>new()</code> can be safely cast with <code>static_cast&lt;QObject*></code>. If we want the derived pointer, we can use <code>qt_metacast()</code>.<br /><pre><br /> // allocate a new QString<br /> void *string;<br /> char *_d1 = "Hello World!";<br /> void *_a1[] = { &string, &_d1 };<br /> smoke_call(strfactory, "new(const char*)", _a1);<br /></pre><br />In order to automate the QString object, the constructor returns a proxy QObject instead of a bare QString, so we can invoke methods on it with <code>qt_metacall()</code>. The question here is how do we get at the bare QString object if it's being wrapped in a QObject? The obvious solution would be to hijack <code>qt_metacast()</code> in the proxy object to break the encapsulation. This would be the only legal method to <em>see</em> a naked QString pointer. It would also allow us to cast up/down the inheritance chain for non-QObject classes by implementing it in <code>qt_metacast()</code>.<br /><pre><br /> void *qstring = qt_metacast_wrapper(string, "QString");<br /> void *button;<br /> void *_a2[] = { &button, qstring };<br /> // and construct the new QPushButton<br /> smoke_call(pbfactory, "new(const QString&)", _a2);<br /></pre><br />Finally, we finish everything off;<br /><pre><br /> int w = 100, h = 30;<br /> void *_a3[] = { 0, &w, &amp;h };<br /> smoke_call(hello, "resize(int,int)", _a3);<br /> smoke_call(hello, "show()", 0);<br /> int ret;<br /> void *_a4[] = { &ret };<br /> smoke_call(app, "exec()", _a4);<br /> return ret;<br />}<br /></pre><br />That covers Hello World. As a perk, every function in every class is now a valid slot. From a language binding, you can even connect() to a function in a non-QObject class.<br /><pre><br />my $str = new QString;<br /># there's no QString::set(const QString&) function?<br />$str->connect($listview, currentTextChanged => 'operator=');<br /></pre><br /><h3>Virtual Functions</h3><br />If we've figured out the calling convention, how do we implement virtual functions? As with Smoke v3, it'll be necessary to override every virtual function in every class. I suspect if you fiddle with gcc4's symbol visibility and compile the virtual-function subclass into the same library as the original function, the linking overhead for doing this should be ~0. That overhead was a nasty performance hit for the smoke library, especially since it was built as a monolithic .so for all of Qt.<br /><br />Each virtual function should emit itself as a signal. By default, each virtual function would be connected to its own slot. When a binding language wants to override a virtual function, it'll be free to connect() the signal to any valid slot. Even a slot in a <em>different</em> object! The binding should take care to disconnect() the default slot connection if the programmer reimplements the virtual function.<br /><br />If the virtual function hasn't been connect()ed up, we're free to optimize away the signal emission if we want and call the original function directly.<br /><br />Just to put some code to this idea...<br /><pre><br />#include &lt;QWidget><br />#include &lt;QSize><br />#include &lt;QCloseEvent><br />#include &lt;QMetaObject><br /><br />namespace SMOKE {<br /><br />class QWidget : public ::QWidget {<br />public:<br /> virtual ::QSize sizeHint() const;<br /> virtual void closeEvent(::QCloseEvent*);<br />};<br /><br />// SMOKE::QWidget implementation<br /><br />::QSize QWidget::sizeHint() const {<br /> ::QSize _ret;<br /> void _a[] = { &_ret };<br /> qt_metacall(::QMetaObject::InvokeMetaMember, metaObject().memberOffset() + 12, _a);<br /> return _ret;<br />}<br /><br />void QWidget::closeEvent(::QCloseEvent *e) {<br /> void _a[] = { 0, &e };<br /> qt_metacall(::QMetaObject::InvokeMetaMember, metaObject().memberOffser() + 13, _a);<br />}<br /><br />}<br /></pre><br /><h3>Method discovery</h3><br />Now that we can call all these methods, we need to know they're there. Somehow we need to provide a listing of available methods to the language bindings. QMetaObject doesn't provide what Smoke needs in this regard.<br /><br />The current Smoke method discovery could be adapted to use the Qt4 moc data-structure, which is just byte offsets into a string which contains every method and type used separated by \0. The Smoke::Index values would be converted into metaObject() offsets. A few other tweaks would be necessary, but moc seems to have similar requirements to Smoke as far as method descriptions go. We'll probably want to keep Smoke3's <code>method$$</code> munging syntax since it's so handy for overload resolution.<br /><h3>Signals and Slots</h3><br />If you notice from my <code>connect()</code> example above, I think we can do away with specifying signal and slot arguments. For signals declared in the binding language, we're going to cheat! Every signal will be declared as having zero arguments. When you call connect(), with a language signal being emitted, the call doesn't get passed on to QObject::connect. Instead, the binding needs to keep track of its own connections. Picture this:<br /><pre><br />sub foo : signal;<br /><br /># this actually declares a function which looks like:<br /><br />sub foo {<br /> my($self, @args);<br /> our %slots;<br /> my @candidates;<br /> for my $slot (@{ $slots{'foo'} }) {<br /> push @candidates, $slot<br /> if compatibleArguments($slot, \@args);<br /> }<br /><br /> # of the slots with the given name which can accept these arguments<br /> # pick the one with the longest string length<br /> @candidates = sort {<br /> length($a->signature) <=> length($b->signature) || $a cmp $b<br /> } @candidates;<br /><br /> magically_call($candidates[0]);<br />}<br /></pre><br />For slots, we can dynamically create slots which match all the matching signals:<br /><pre><br />$foo->connect($bar, signalName => slotName);<br /><br /># signalName(const QString&)<br /># signalName(int)<br />sub slotName : slot {<br /> # this function gets called for either/both of those signals,<br /> # unless it specifies a signature in the slot declaration<br />}<br /></pre><br />That exhausts all I've thought through so far.Ashnoreply@blogger.com