custom - signals and slots qt
¿Cómo se implementan la señal y las ranuras debajo del capó? (2)
Creo que debería agregar lo siguiente.
Hay otra pregunta relacionada , y hay un artículo muy bueno que puede considerarse como una expansión bastante detallada de su answer ; aquí está este artículo otra vez , con resaltado de sintaxis de código mejorado (aunque no perfecto).
Aquí está mi breve recuento de él, que puede ser propenso a errores)
Básicamente, cuando insertamos la macro Q_OBJECT
en nuestra definición de clase, el preprocesador la expande a una declaración de instancia de QMetaObject
estática, que sería compartida por todas las instancias de la misma clase:
class ClassName : public QObject // our class definition
{
static const QMetaObject staticMetaObject; // <--= Q_OBJECT results to this
// ... signal and slots definitions, other stuff ...
}
Esta instancia, a su vez, en la inicialización almacenará las firmas ( "methodname(argtype1,argtype2)"
) de las señales y las ranuras, lo que permitirá implementar la llamada indexOfMethod()
, que devuelve, bueno, el índice del método por su firma cuerda :
struct Q_CORE_EXPORT QMetaObject
{
// ... skip ...
int indexOfMethod(const char *method) const;
// ... skip ...
static void activate(QObject *sender, int signal_index, void **argv);
// ... skip ...
struct { // private data
const QMetaObject *superdata; // links to the parent class, I guess
const char *stringdata; // basically, "string1/0string2/0..." that contains signatures and other names
const uint *data; // the indices for the strings in stringdata and other stuff (e.g. flags)
// skip
} d;
};
Ahora cuando el moc
crea el archivo moc_headername.cpp
para el encabezado de la clase Qt headername.h
, pone allí las cadenas de firmas y otros datos que son necesarios para la inicialización correcta de la estructura d
, y luego escribe el código de inicialización para el singleton staticMetaObject
usando esta información
Otra cosa importante que hace es generar el código para el método qt_metacall()
del objeto, que toma el ID de método de un objeto y una matriz de punteros de argumento y llama al método a través de un switch
largo como este:
int ClassName::qt_metacall(..., int _id, void **_args)
{
// ... skip ...
switch (_id) {
case 0: signalOrSlotMethod1(_args[1], _args[2]); break; // for a method with two args
case 1: signalOrSlotMethod2(_args[1]); break; // for a method with a single argument
// ... etc ...
}
// ... skip ...
}
Por último, para cada moc
señal se genera una implementación que contiene una QMetaObject::activate()
:
void ClassName::signalName(argtype1 arg1, argtype2 arg2, /* ... */)
{
void *_args[] = { 0, // this entry stands for the return value
&arg1, // actually, there''s a (void*) type conversion
&arg2, // in the C++ style
// ...
};
QMetaObject::activate( this,
&staticMetaObject,
0, /* this is the signal index in the qt_metacall() map, I suppose */
_args
);
}
Finalmente, la llamada a connect()
traduce las firmas de método de cadena a sus identificadores enteros (los utilizados por qt_metacall()
) y mantiene una lista de conexiones de señal a ranura; cuando se emite la señal, el código activate()
pasa por esta lista y llama al objeto apropiado "slots" a través de su método qt_metacall()
.
En resumen, la instancia QMetaObject
estática almacena la "metainformación" (cadenas de firma de métodos, etc.), un método qt_metacall()
generado proporciona "una tabla de métodos" que permite llamar a cualquier señal / ranura mediante un índice, implementaciones de señal generadas con moc
use estos índices a través de activate()
, y finalmente connect()
hace el trabajo de mantener una lista de mapas de índice de señal a ranura.
* Nota: hay una complicación de este esquema utilizado para el caso cuando queremos entregar señales entre diferentes hilos (sospecho que hay que mirar el código blocking_activate()
), pero espero que la idea general siga siendo la misma)
Esta es mi comprensión muy aproximada del artículo vinculado, que puede ser incorrecto, así que recomiendo ir y leerlo directamente)
PD. Como me gustaría mejorar mi comprensión de la implementación de Qt, avísenme sobre cualquier incoherencia en mi recuento.
Como mi otra respuesta (anterior) fue eliminada por algún editor entusiasta, añado el texto aquí (me faltan algunos detalles que no fueron incorporados en la publicación de Pavel Shved, y dudo que a la persona que eliminó la respuesta le importara).
@Pavel Shved:
Estoy bastante seguro de que en algún lugar de los encabezados Qt existe una línea:
#define emit
Solo para confirmar: lo encontré en el antiguo código Qt de Google Code Search. Es bastante probable que todavía esté allí); la ruta de ubicación encontrada fue:
ftp://ftp.slackware-brasil.com.br > slackware-7.1> contrib> kde-1.90> qt-2.1.1.tgz> usr> lib> qt-2.1.1> src> kernel> qobjectdefs.h
Otro enlace de complementación: http://lists.trolltech.com/qt-interest/2007-05/thread00691-0.html - vea la respuesta de Andreas Pakulat
Y aquí hay otra parte de la respuesta: Qt pregunta: ¿Cómo funcionan las señales y las ranuras?
Esta pregunta ya se hizo en este foro, pero no entiendo el concepto.
Estaba leyendo y parece que la señal y las ranuras se implementan usando punteros de función, es decir, la señal es una función grande que en su interior llama a todas las ranuras conectadas (punteros de función). ¿Es esto correcto? ¿Y cuál es el papel de los archivos moc generados en toda la historia? No entiendo cómo la función de señal sabe qué ranuras llamar, es decir, qué ranuras están conectadas a esta señal.
Gracias por tu tiempo
Qt implementa estas cosas de una manera que se asemeja a los idiomas interpretados. Es decir, construye tablas de símbolos que asignan nombres de señales a punteros de función, los mantiene y busca el puntero de función por nombre de función cuando sea necesario.
Cada vez que emita una señal, es decir, escriba
emit something();
en realidad se llama a la función something()
, que se genera automáticamente mediante el compilador de metaobjetos y se coloca en un archivo *.moc
. Dentro de esta función se comprueba a qué ranuras está conectada esta señal en este momento, y las funciones de ranura apropiadas (que ha implementado en sus propias fuentes) se llaman secuencialmente a través de las tablas de símbolos (en la forma descrita anteriormente). Y emit
, al igual que otras palabras clave específicas de Qt, simplemente son descartadas por el preprocesador de C ++ después de generar *.moc
. De hecho, en uno de los encabezados Qt ( qobjectdefs.h ), existen tales líneas:
#define slots
#define signals protected
#define emit
La función de conexión ( connect
) simplemente modifica las tablas de símbolos mantenidas dentro de los archivos *.moc
, y los argumentos que se le pasan (con las macros SIGNAL()
y `SLOT ''también se preprocesan para que coincidan con las tablas.
Esa es la idea general. En su respuesta, ジョージ nos proporciona enlaces a la lista de correo trolltech y a otra pregunta sobre este tema.