Подтвердить что ты не робот

Как сигналы и слоты реализованы под капотом?

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

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

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

4b9b3361

Ответ 1

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

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

emit something();

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

#define slots 
#define signals protected
#define emit

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

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

Ответ 2

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

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

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

В основном, когда мы вставляем макрос 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 singleton с использованием этих данных.

Еще одна важная вещь - генерация кода для метода object 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 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() предоставляет "таблицу методов", которая позволяет любому сигналу/слоту вызываться посредством индекс, реализации сигналов, сгенерированные с помощью moc, используют эти индексы через activate(), и, наконец, connect() выполняет работу по поддержанию списка карт индексов "сигнал-слот".

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

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

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


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

@Pavel Shved:

Я уверен, что где-нибудь в заголовках 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: Как работают сигналы и слоты?