Как сигнал и слоты реализованы под капотом? - PullRequest
23 голосов
/ 10 сентября 2009

Этот вопрос уже задавался на этом форуме, но я не понимаю концепцию.

Я читал, и кажется, что сигнал и слоты реализованы с использованием указателей функций, т.е. сигнал - это одна большая функция, которая внутри него вызывает все подключенные слоты (указатели функций). Это правильно? И какова роль сгенерированных файлов moc во всей истории? Я не понимаю, как функция сигнала знает, какие слоты вызывать, т.е. какие слоты подключены к этому сигналу.

Спасибо за ваше время

Ответы [ 2 ]

16 голосов
/ 11 сентября 2009

Qt реализует эти вещи способом, похожим на интерпретируемые языки. То есть он создает таблицы символов, которые отображают имена сигналов на указатели функций, поддерживает их и ищет указатель функции по имени функции, где это необходимо.

Каждый раз, когда вы излучаете сигнал, то есть пишите

emit something();

вы на самом деле вызываете функцию something(), которая автоматически генерируется мета-объектным компилятором и помещается в файл *.moc. В рамках этой функции проверяется, к каким слотам данный сигнал подключен в данный момент, и соответствующие функции слотов (которые вы реализовали в своих собственных источниках) последовательно вызываются через таблицы символов (как описано выше). И emit, как и другие специфичные для Qt ключевые слова, просто отбрасываются препроцессором C ++ после генерации *.moc. Действительно, в одном из заголовков Qt ( qobjectdefs.h ) существуют такие строки:

#define slots 
#define signals protected
#define emit

Функция соединения (connect) просто изменяет таблицы символов, хранящиеся в файлах *.moc, и передаваемые ей аргументы (с макросами SIGNAL() и `SLOT) также предварительно обрабатываются для соответствия таблицам.

Это общая идея. В своем другом ответе ジ ョ ー ジ предоставляет нам ссылки на список рассылки trolltech и на другой SO * вопрос по этой теме.

5 голосов
/ 07 марта 2012

Думаю, мне следует добавить следующее.

Есть еще один связанный вопрос - и очень хорошая статья , которую можно рассматривать как довольно подробное расширение к ответу ; вот снова эта статья с улучшенной (хотя и не идеальной) подсветкой синтаксиса кода.

Вот мой короткий пересказ, который может быть склонен к ошибкам) ​​

Обычно, когда мы вставляем макрос Q_OBJECT в определение нашего класса, препроцессор расширяет его до статического объявления экземпляра QMetaObject, которое будет общим для всех экземпляров одного и того же класса:

class ClassName : public QObject // our class definition
{
    static const QMetaObject staticMetaObject; // <--= Q_OBJECT results to this

    // ... signal and slots definitions, other stuff ...

}

Этот экземпляр, в свою очередь, при инициализации будет хранить сигнатур ("methodname(argtype1,argtype2)") сигналов и слотов, что позволит реализовать вызов indexOfMethod(), который возвращает, ну, в общем, метод индекс по строке подписи:

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;
};

Теперь, когда moc создает файл moc_headername.cpp для заголовка класса Qt headername.h, он помещает туда строки подписи и другие данные, необходимые для правильной инициализации структуры d, а затем записывает код инициализации синглтона staticMetaObject, использующий эти данные.

Еще одна важная вещь, которую он делает, - это генерация кода для метода qt_metacall() объекта, который принимает идентификатор метода объекта и массив указателей на аргументы и вызывает метод через длинный switch, например:

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 ...
}

Последнее, для каждого сигнала moc генерирует реализацию, которая содержит 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
                         );
}

Наконец, вызов connect() преобразует сигнатуры строковых методов в их целочисленные идентификаторы (используемые qt_metacall()) и поддерживает список соединений между сигналами и слотами; когда сигнал испускается, код activate() проходит через этот список и вызывает соответствующий слот объекта через их метод qt_metacall().

Подводя итог, статический экземпляр QMetaObject хранит «метаинформацию» (строки сигнатур метода и т. Д.), Сгенерированный метод qt_metacall() предоставляет «таблицу методов», которая позволяет любому сигналу / слоту вызываться index, реализации сигналов, генерируемые moc, используют эти индексы через activate(), и, наконец, connect() выполняет работу по поддержанию списка отображений индекса сигнал-слот.

* Примечание: есть сложность этой схемы, используемая для случая, когда мы хотим доставлять сигналы между различными потоками (я подозреваю, что нужно смотреть на код blocking_activate()), но я надеюсь, что общая идея остается той же )

Это мое очень грубое понимание связанной статьи, которая может легко ошибаться, поэтому я рекомендую пойти и прочитать ее напрямую)

PS. Поскольку я хотел бы улучшить мое понимание реализации Qt, пожалуйста, сообщите мне о любых несоответствиях в моем пересказе!


Поскольку мой другой (более ранний) ответ был удален каким-то ревностным редактором, я добавлю сюда текст (мне не хватает нескольких деталей, которые были не включены в пост Павла Шведа, и я сомневаюсь в человеке, который удалил ответ позаботился.)

@ Павел Швед:

Я почти уверен, что где-то в заголовках Qt есть строка:

#define emit

Просто для подтверждения: нашел его в старом коде Qt с помощью Google Code Search. Вполне вероятно, что он все еще там); найденный путь был:

FTP: //ftp.slackware-brasil.com.br> Slackware-7.1> вно> КДЭ-1,90> кварты-2.1.1.tgz> USR> Lib> кварты-2.1.1> ЦСИ> ядро> qobjectdefs.h


Еще одна дополнительная ссылка: http://lists.trolltech.com/qt-interest/2007-05/thread00691-0.html - см. Ответ Андреаса Пакулата


А вот еще один ответ: Вопрос Qt: Как работают сигналы и слоты?

...