[Kde-bindings] custom slots
Richard Dale
Richard_Dale at tipitina.demon.co.uk
Fri Jan 27 13:29:24 UTC 2006
On Friday 27 January 2006 11:05, Thomas Moenicke wrote:
> Richard Dale wrote:
> > Another way is to override the QObject::metaObject() virtual method in
> > php subclasses of the QObject classes, and return a dynamically
> > constructed QMetaObject with any slots and signals that were defined in
> > php. Then override the qt_metacall() virtual method, and for any php
> > slots marshall the args from C++ to php, and call the php method. If the
> > qt_metacall() wasn't for a php slot, then call qt_metacall() in the super
> > class, so that it will just call an existing C++ slot. Given a list of
> > slot and signal type signatures as strings it is reasonably
> > straightforward to construct a QMetaObject.
>
> Everything ok, but how to define such a metaobject at runtime? Or more
> precisely how to add slots defined as php methods to a metaobject? Here I
> have the same problem as descibed in my previous mail, or am I wrong with
> that?
You don't add extra slots and signals to an existing QMetaObject, instead you
construct a new QMetaObject with the php slots and the existing QMetaObject
as its parent. Then keep a hash of class name vs. QMetaObject instance.
Override the QObject::metaObject() virtual method, and return the QMetaObject
for that class name from the hash. If there is no entry in the hash yet for
the class, then you construct a QMetaObject instance.
> > The disadvantage of using proxy methods is that you need to intervene
> > everytime a slot or signal is passed to a Qt method, and do something
> > different depending on whether the target slot is a C++ on or a php one.
> > Whereas if you construct QMetaObjects at runtime from the slots and
> > signals declared in php, you only need to override the qt_metacall()
> > method.
>
> Ok. Lets point that out, this is a copy of my experiment:
>
> int Bridge::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
> {
>
> _id = QWidget::qt_metacall(_c, _id, _a);
>
> if (_id < 0)
> return _id;
> if (_c == QMetaObject::InvokeMetaMethod) {
> switch (_id) {
> case 0: { int _r = check_signal(*reinterpret_cast< int*>(_a[1]));
> if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; } break;
> // case 1: check_slot(*reinterpret_cast< int*>(_a[1])); break;
> case 1: (this->*slot_functions[0])(*reinterpret_cast<
> int*>(_a[1])); break;
> }
> _id -= 2;
> }
> return _id;
> }
>
> qt_metacall looks up in any data structure where a pointer to the php
> function is stored. Therefor I need to register the right id for this
> custom slot. Well, I can override qt_metacall, but it will only be called
> if Qt finds a name of the slot in their data structures.
> Now the question is how to construct metaobjects at runtime, with the names
> of all custom slots inside.
>
> > Given a list of slot and signal
> > type signatures as strings it is reasonably straightforward to construct
> > a QMetaObject.
>
> Thus, modify this?
>
> static const uint qt_meta_data_Bridge[] = {
>
> // content:
> 1, // revision
> 0, // classname
> 0, 0, // classinfo
> 2, 10, // methods
> 0, 0, // properties
> 0, 0, // enums/sets
>
> // signals: signature, parameters, type, tag, flags
> 21, 12, 8, 7, 0x05,
>
> // slots: signature, parameters, type, tag, flags
> 45, 39, 7, 7, 0x0a,
>
> 0 // eod
> };
>
> static const char qt_meta_stringdata_Bridge[] =
> {
> "Bridge\0\0int\0newValue\0check_signal(int)\0value\0check_slot(int)\0myowns
>tuff\0" };
No, you don't need to modify the QMetaObject. Here is the QtRuby code to build
the tables of null terminated strings, and numeric offsets like the one
above:
#
# From the enum MethodFlags in qt-copy/src/tools/moc/generator.cpp
#
AccessPrivate = 0x00
AccessProtected = 0x01
AccessPublic = 0x02
MethodMethod = 0x00
MethodSignal = 0x04
MethodSlot = 0x08
MethodCompatibility = 0x10
MethodCloned = 0x20
MethodScriptable = 0x40
# Keeps a hash of strings against their corresponding offsets
# within the qt_meta_stringdata sequence of null terminated
# strings. Returns a proc to get an offset given a string.
# That proc also adds new strings to the 'data' array, and updates
# the corresponding 'pack_str' Array#pack template.
def Internal.string_table_handler(data, pack_str)
hsh = {}
offset = 0
return lambda do |str|
if !hsh.has_key? str
hsh[str] = offset
data << str
pack_str << "a*x"
offset += str.length + 1
end
return hsh[str]
end
end
def Internal.makeMetaData(classname, signals, slots)
# Each entry in 'stringdata' corresponds to a string in the
# qt_meta_stringdata_<classname> structure.
# 'pack_string' is used to convert 'stringdata' into the
# binary sequence of null terminated strings for the metaObject
stringdata = []
pack_string = ""
string_table = string_table_handler(stringdata, pack_string)
# This is used to create the array of uints that make up the
# qt_meta_data_<classname> structure in the metaObject
data = [1, # revision
string_table.call(classname), # classname
0, 0, # classinfo
signals.length + slots.length, 10, # methods
0, 0, # properties
0, 0] # enums/sets
signals.each do |entry|
data.push string_table.call(entry.full_name) # signature
data.push string_table.call(entry.full_name.delete("^,")) # parameters
data.push string_table.call("") # type, "" means void
data.push string_table.call("") # tag
data.push MethodSignal | AccessProtected # flags, always protected for now
end
slots.each do |entry|
data.push string_table.call(entry.full_name) # signature
data.push string_table.call(entry.full_name.delete("^,")) # parameters
data.push string_table.call("") # type, "" means void
data.push string_table.call("") # tag
data.push MethodSlot | AccessPublic # flags, always public for now
end
data.push 0 # eod
return [stringdata.pack(pack_string), data]
end
def Internal.getMetaObject(qobject)
meta = Meta[qobject.class.name]
return nil if meta.nil?
if meta.metaobject.nil? or meta.changed
signals = meta.get_signals
slots = meta.get_slots
stringdata, data = makeMetaData(qobject.class.name, signals, slots)
meta.metaobject = make_metaObject(qobject, stringdata, data)
meta.changed = false
end
meta.metaobject
end
end # Qt::Internal
So 'Meta' is a hash, and first it looks in there, and if the QMetaObject is
missing, then it creates one with the above Ruby code. That in turn calls a C
function make_metaObject(qobject, stringdata, data) which actually constructs
the C++ QMetaObject. Here is the code that does that:
static VALUE
make_metaObject(VALUE /*self*/, VALUE obj, VALUE stringdata_value, VALUE
data_value)
{
smokeruby_object *o = value_obj_info(obj);
if (!o || !o->ptr) {
rb_raise(rb_eRuntimeError, "Cannot create metaObject\n");
}
Smoke::Index nameId = o->smoke->idMethodName("metaObject");
Smoke::Index meth = o->smoke->findMethod(o->classId, nameId);
if (meth <= 0) {
// Should never happen..
rb_raise( rb_eRuntimeError,
"Cannot find %s::metaObject() method\n",
o->smoke->classes[o->classId].className );
}
Smoke::Method &methodId =
o->smoke->methods[o->smoke->methodMaps[meth].method];
Smoke::ClassFn fn = o->smoke->classes[methodId.classId].classFn;
Smoke::StackItem i[1];
(*fn)(methodId.method, o->ptr, i);
QMetaObject *superdata = (QMetaObject *) i[0].s_voidp;
char *stringdata = new char[RSTRING(stringdata_value)->len];
int count = RARRAY(data_value)->len;
uint * data = new uint[count];
memcpy( (void *) stringdata, RSTRING(stringdata_value)->ptr,
RSTRING(stringdata_value)->len );
for (long i = 0; i < count; i++) {
VALUE rv = rb_ary_entry(data_value, i);
data[i] = NUM2UINT(rv);
}
QMetaObject ob = {
{ superdata, stringdata, data, 0 }
} ;
QMetaObject * meta = new QMetaObject;
*meta = ob;
smokeruby_object * m = (smokeruby_object *)
malloc(sizeof(smokeruby_object));
m->smoke = qt_Smoke;
m->classId = qt_Smoke->idClass("QMetaObject");
m->ptr = meta;
m->allocated = true;
return Data_Wrap_Struct(qt_qmetaobject_class, smokeruby_mark,
smokeruby_free, m);
}
First it calls the QObject::metaObject() method via the Smoke library on the
parent class to get the parent QMetaObject ('superdata'). Then it unwraps the
C data from the ruby arguments passed, and constructs the QMetaObject with
this call:
QMetaObject ob = {
{ superdata, stringdata, data, 0 }
} ;
QMetaObject * meta = new QMetaObject;
*meta = ob;
Finally it wraps the new instance as a Ruby object and returns it to qtruby.rb
so it can be added to the classname to QMetaObject instance hash.
So once, the new QMetaObject is returned to the Qt runtime, it is possible to
connect slots and signals using it. In qt_metacall() the code checks whether
or not the target slot is a C++ or ruby one like this:
static VALUE
qt_metacall(int /*argc*/, VALUE * argv, VALUE self)
{
// Arguments: QMetaObject::Call _c, int id, void ** _o
QMetaObject::Call _c = (QMetaObject::Call) NUM2INT( rb_funcall(
qt_internal_module,
rb_intern("get_qinteger"),
1,
argv[0] ) );
int id = NUM2INT(argv[1]);
void ** _o = 0;
// Note that for a slot with no args and no return type,
// it isn't an error to get a NULL value of _o here.
Data_Get_Struct(argv[2], void*, _o);
VALUE metaObject_value = rb_funcall(qt_internal_module,
rb_intern("getMetaObject"), 1, self);
smokeruby_object *ometa = value_obj_info(metaObject_value);
if (!ometa) return argv[1];
QMetaObject *metaobject = (QMetaObject*)ometa->ptr;
int count = metaobject->methodCount();
int offset = metaobject->methodOffset();
if (id < offset) {
// Assume the target slot is a C++ one
...
If the id passed to qt_metacall is less than the start offset for the signals
in the dynamically constructed QMetaObject for ruby slots, then it must be a
C++. Otherwise, it drops through the 'id < offset' test and the code
marshalls the C++ args in '_o' to ruby ones and calls the ruby method.
-- Richard
More information about the Kde-bindings
mailing list