tag: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.<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/11264158-111837982603897170?l=jahqueel.blogspot.com'/></div>Ashnoreply@blogger.com1