Qyoto and Kimono C# Bindings
Richard Dale
KDE RAD tools developer
KDE Bindings Since Akademy 2005
- Trolltech have announced Qt Jambi Java bindings and generation tool
- I've been working on the Qyoto bindings with Arno Rehn, Paolo Capriotti and others
- Thomas Monicke is working on PHP bindings for Qt/KDE based on Smoke
- PyQt and QtRuby for Qt 4.x released and stable
- The KDevelop C++ parser framework is mature and complete
The Smoke Library
- Stands for 'Scripting Meta Object Kompiler Engine'
- A 'moc on steriods'
- Originally designed by Ashley Winters for the PerlQt bindings
- The Qt moc utility adds introspection for slots, signals and properties QObject classes
Smoke Features
- Introspection for every method
- Accessor methods for instance variables
- Function calls to obtain the value of enums
- Callbacks for overloading virtual methods
- Cleanup callbacks from C++ destructors
- Handles multiple inheritance
- Bindings language values are marshalled to and from a language independent 'Smoke::Stack'
Using Smoke with QtRuby
- If you call a method which isn't there on a Ruby instance it is diverted to 'method_missing()'
- In method_missing(), the method is looked up the the Smoke runtime
- Ruby arguments are marshalled to C++ ones and pushed onto the 'Smoke::Stack'
- The C++ method is invoked and any return value is marshalled from C++ to Ruby
Overriding Virtual methods
- If you override a virtual method, Smoke has language independent callbacks
- It checks if you have overriden the method in the bindings language
- The C++ arguments are marshalled to Ruby ones the method is invoked
- Any result is marshalled from Ruby to C++
Calling Slots and Signals
- If you declare slots and signals in a Ruby class a QMetaObject is constructed at runtime
- The QMetaObject is identical to the one generated by the moc utility
- Identical from the point of view of the Qt runtime
Conventional C# Bindings Design
- The Qt# bindings used the QtC C bindings
- P/Invoke is designed to allow C# to work with C
- It doesn't handle unmanaged C++ well
- No standard way to deal with method overloading
- C++ name mangling means you need to implement C bindings
- Each C# method calls a C method, which in turn calls the target C++ method
Hello World In Qyoto
using System;
using Qyoto;
public class T1
{
public static int Main(String[] args) {
QApplication a = new QApplication(args);
QPushButton hello = new QPushButton("Hello world!", null);
hello.Resize(100, 30);
hello.Show();
return QApplication.Exec();
}
}
Using Smoke with C#
- No C bindings library is needed
- Each C# instance has a transparent proxy
- Each C# class has a transparent proxy for static methods
Transparent Proxies as Interceptors
protected new void CreateProxy() {
SmokeInvocation realProxy = new SmokeInvocation(typeof(QApplication), this);
_interceptor = (QApplication) realProxy.GetTransparentProxy();
}
private QApplication ProxyQApplication() {
return (QApplication) _interceptor;
}
private static Object _staticInterceptor = null;
static QApplication() {
SmokeInvocation realProxy = new SmokeInvocation(typeof(IQApplicationProxy), null);
_staticInterceptor = (IQApplicationProxy) realProxy.GetTransparentProxy();
}
private static IQApplicationProxy StaticQApplication() {
return (IQApplicationProxy) _staticInterceptor;
}
...
[SmokeMethod("isSessionRestored() const")]
public bool IsSessionRestored() {
return ProxyQApplication().IsSessionRestored();
}
SmokeInvocation
- All calls made on the interceptors are forwarded to SmokeInvocation.Invoke()
public class SmokeInvocation : RealProxy {
...
public override IMessage Invoke(IMessage message) {
IMethodCallMessage callMessage = (IMethodCallMessage) message;
#if DEBUG
Console.WriteLine( "ENTER Invoke() MethodName: {0} Type: {1} ArgCount: {2}",
callMessage.MethodName,
callMessage.TypeName,
callMessage.ArgCount.ToString() );
#endif
...
}
Smoke Library Method Lookup
- First the idMethodName() call obtains a unique number for a method
- This is used in conjunction with a class id to obtain a pointer to a function
-
Smoke::Index nameId = qt_Smoke->idMethodName("qt_metacall$$?");
Smoke::Index meth = qt_Smoke->findMethod(classId, nameId);
if(meth > 0) {
Smoke::Method &m = qt_Smoke->methods[qt_Smoke->methodMaps[meth].method];
Smoke::ClassFn fn = qt_Smoke->classes[m.classId].classFn;
Smoke::StackItem i[4];
i[1].s_enum = _c;
i[2].s_int = _id;
i[3].s_voidp = _o;
(*fn)(m.method, o->ptr, i);
return i[0].s_int;
}
Emitting a signal
- For each QObject class there is an Emit() method
- Emit() returns a transparent proxy
protected QObject(Type dummy) {
try {
Type proxyInterface = Qyoto.GetSignalsInterface(GetType());
SignalInvocation realProxy = new SignalInvocation(proxyInterface, this);
Q_EMIT = realProxy.GetTransparentProxy();
}
catch {
Console.WriteLine("Could not retrieve signal interface");
}
}
All signal invocations are forwarded to SignalInvocation.Invoke()
C# Signal Example
class CannonField : QWidget {
...
protected new ICannonFieldSignals Emit() {
return (ICannonFieldSignals) Q_EMIT;
}
...
Emit().ForceChanged(currentForce);
...
}
public interface ICannonFieldSignals : IQWidgetSignals {
[Q_SIGNAL("void forceChanged(int)")]
void ForceChanged(int force);
}
C# Slot Example
[Q_SLOT("void setValue(int)")]
public void SetValue(int value) {
slider.SetValue(value);
}
...
Connect(field, SIGNAL("angleChanged(int)"), angle, SLOT("setValue(int)"));
Aspect Oriented Programming
- Qyoto and Kimono have been designed from the ground up for AOP
- Each method call can be a join point
- Each slot or signal invocation can be a join point
- QtRuby and Qyoto share the same Smoke library
- Point cuts are specified as a Ruby DSL
- Advice code can be written in both Ruby and C#