[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