Russian Qt Forum

Qt => Общие вопросы => Тема начата: Гурман от Сентябрь 01, 2014, 17:36



Название: Как обработать сигнал одним слотом из подключенных?
Отправлено: Гурман от Сентябрь 01, 2014, 17:36
Есть несколько одинаковых объектов-приемников, у которых есть некий слот. Ко всем этим объектам подключен один сигнал одного объекта-источника, одновременно. В норме активация сигнала приводит к получению его всеми слотами и обработке всеми объектами этого класса. Точнее, всеми экземплярами объектов, как я сказал, их несколько. Но нужно, чтобы сигнал обработался только одним объектом, который получит сигнал первым, а остальные его проигнорировали. Проблема в том, что спецификатор static для создания разделяемого данного, и использования как семафора, не подходит - все объекты, это загруженные Qt-плагины. А в вениках, как известно, память данных у каждой DLL своя.

И отсюда вопрос - можно ли как-то внутри экземпляра объекта узнать, что он получил сигнал не первым? Вроде было такое где-то в QMeta???, но что-то не получается найти.


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Old от Сентябрь 01, 2014, 17:39
А зачем подключать слоты объектов к сигналу, если они его должны игнорировать?
Почему бы не подключать только один из объектов?


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Bepec от Сентябрь 01, 2014, 18:04
Т.к. слоты подключают сигнал в очередности их подключения (connect), то затея теряет смысл :) Всегда будет обрабатываться один и тот же слот.

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


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Гурман от Сентябрь 01, 2014, 18:48
А зачем подключать слоты объектов к сигналу, если они его должны игнорировать?
Почему бы не подключать только один из объектов?

Разные объекты в плагинах, какие-то могут не быть загружены (отстуствовать в поставке). Могут присутствовать несколько. А обработать сигнал должен только один.


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Гурман от Сентябрь 01, 2014, 18:53
Т.к. слоты подключают сигнал в очередности их подключения (connect), то затея теряет смысл :) Всегда будет обрабатываться один и тот же слот.

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

Что один и тот же - плевать, важно, чтобы сигнал не получили или проигнорировали остальные.

С общим ресурсом хреново... его можно только в головном приложении выделить, но дюже муторно получается. Сигналы получают плагины, а они по идее, максимально независимы от головного приложения, обмениваются данными только через сигнал-слоты.

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


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Old от Сентябрь 01, 2014, 19:08
Можно проверять сигнал перед коннектом: если к нему уже кто-то подключен, то не подключаться.
bool QObject::isSignalConnected(const QMetaMethod & signal) const [protected]


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Гурман от Сентябрь 01, 2014, 19:29
Можно проверять сигнал перед коннектом: если к нему уже кто-то подключен, то не подключаться.
bool QObject::isSignalConnected(const QMetaMethod & signal) const [protected]

Не, нельзя... Задача сложнее - первый, кто получил сигнал, становится занят на неопределенное время, за которое может прийти следующий сигнал, значит надо, чтобы сигнал получил другой, и обработал, если свободен. Слишком сложная получается логика подключений-отключений. Пока без invokeMethod не понятно, как это можно иначе сделать.


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: m_ax от Сентябрь 01, 2014, 22:01
Можно проверять сигнал перед коннектом: если к нему уже кто-то подключен, то не подключаться.
bool QObject::isSignalConnected(const QMetaMethod & signal) const [protected]

Не, нельзя... Задача сложнее - первый, кто получил сигнал, становится занят на неопределенное время, за которое может прийти следующий сигнал, значит надо, чтобы сигнал получил другой, и обработал, если свободен. Слишком сложная получается логика подключений-отключений. Пока без invokeMethod не понятно, как это можно иначе сделать.

Сделайте промежуточное звено (менеджер сигналов).. Пусть ловит сигналы и уже после (исходя из вашей задачи) перенаправляет их кому нужно и как нужно.. 


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: __Heaven__ от Сентябрь 02, 2014, 06:59
А нельзя ли объявить глобальную переменную-семафор, а при первом вызове метода осуществить дисконнект от прочих слотов?


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: GreatSnake от Сентябрь 02, 2014, 07:32
А нельзя ли объявить глобальную переменную-семафор, а при первом вызове метода осуществить дисконнект от прочих слотов?
Выстави QApplication::property().


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Igors от Сентябрь 02, 2014, 08:47
Самое простое наверное и будет самым лучшим - подать флажок "принято" в параметрах сигнала. Напр использовать boost::optional


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Гурман от Сентябрь 02, 2014, 10:31
Сделайте промежуточное звено (менеджер сигналов).. Пусть ловит сигналы и уже после (исходя из вашей задачи) перенаправляет их кому нужно и как нужно..  

В этом нет смысла, так менеджить сигналы можно было бы и в отправителе. Если бы можно было. Но такой вариант слишком усложняет реализацию.


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Гурман от Сентябрь 02, 2014, 10:43
Самое простое наверное и будет самым лучшим - подать флажок "принято" в параметрах сигнала. Напр использовать boost::optional

boost не использую

сигнал - не объект, где у него есть состояние "принято"?

а насчет флажка в данных сигнала - я изначально считаю, что каждый слот, подключенный к одному сигналу, получает свою копию данных, то есть, флажок не является глобальным для принимающих объектов

а разве нет?


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Bepec от Сентябрь 02, 2014, 11:32
Цитировать
Выстави QApplication::property().
qApp один на всю программу. Соответственно и флажок будет одним.


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: navrocky от Сентябрь 02, 2014, 11:39
В сигнале передай ссылку на структуру с флажком обработанности и полезными данными. В слоте будешь проверять, что сигнал уже обработали по этому флажку. При обработке флажок взводить.

Для больше безопасности её лучше завернуть в shared_ptr.

Код
C++ (Qt)
struct MySignalData
{
   bool processed;
   QString payload;
};
 
typedef QSharedPointer<MySignalData> MySignalDataPtr;
Q_DECLARE_METATYPE(MySignalDataPtr)
 
....
 
int main(int argc, char **argv)
{
   qRegisterMetaType<MySignalDataPtr>();
 
....
 
signals:
   void mySignal(MySignalDataPtr);
....
 
void mySlot(MySignalDataPtr data)
{
   if (data->processed)
       return;
   processData(data);
   data->processed = true;
}
 


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: GreatSnake от Сентябрь 02, 2014, 11:43
Цитировать
Выстави QApplication::property().
qApp один на всю программу. Соответственно и флажок будет одним.
Внимательно прочитай вопрос, на который был мой ответ.

Ко всем этим объектам подключен один сигнал одного объекта-источника, одновременно.
Если позволяет архитектура, такой флажок можно завести в объекте-источнике ( sender() ).


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Гурман от Сентябрь 02, 2014, 11:51
Уже сделал на invokeMethod(). Проще всего, ничего лишнего. Реестр плагинов имеется, нашел в нем первый подходящий и не занятый, и в нем вызвал метод. У всех подходящих плагинов имя этого метода одинаковое, поскольку наследуют от одного класса. Всё. Работает. Даже устанавливать соединения не требуется.


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Гурман от Сентябрь 02, 2014, 11:55
Ко всем этим объектам подключен один сигнал одного объекта-источника, одновременно.
Если позволяет архитектура, такой флажок можно завести в объекте-источнике ( sender() ).

В архитектуре постулировано, что плагины с родительским приложением и наоборот общаются только через сигнал-слоты. А они при обычных соединениях значения не возвращают. Но вот invoikeMethod() позволяет получить возвращаемое значение, хотя является эквивалентом временного соединения сигнал-слот, поскольку работает на том же механизме.


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Igors от Сентябрь 02, 2014, 11:57
Уже сделал на invokeMethod(). Проще всего, ничего лишнего. Реестр плагинов имеется, нашел в нем первый подходящий и не занятый, и в нем вызвал метод. У всех плагинов имя этого метода одинаковое, поскольку наследуют от одного класса. Всё. Работает. Даже устанавливать соединения не требуется.
Безыдейно (хотя и просто и работает)


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Гурман от Сентябрь 02, 2014, 11:59
Безыдейно (хотя и просто и работает)

Принцип бритвы Оккама - самое простое решение является самым правильным.  ;D


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: GreatSnake от Сентябрь 02, 2014, 12:04
Безыдейно (хотя и просто и работает)
Имхо в данном случае самое простое решение.
И если бы автор изначально по-конкретнее поставил задачу, такого количества костылей ему бы не было предложено  :)


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Гурман от Сентябрь 03, 2014, 14:21
Хе... Заметил попутно такой факт, не совсем в тему, но в принципе можно было бы использовать и тут - если класс хранения переменной в плагине статический (static, или глобально без модификатора, или extern), и плагин загружается из той же DLL повторно, то эта переменная будет общей для всех загруженных версий плагина (динамическая память и стек у каждого будут, разумеется, свои). Но если плагин загрузить с таким же кодом, но из DLL с другим именем (из копии файла DLL), то статически хранимые переменные будут для всех версий свои. По крайней мере, так в вениках. Как в Линухе еще не знаю, до сборки проекта в нем еще далеко.

По идее, все правильно, поскольку статически хранимые переменные размещаются в общем сегменте данных, который вместе с сегментом кода отображается в оперативную память процессора. Но неоднократно читал (делать такое не приходилось), что в вениках такие переменные у каждого экземпляра плагина свои - правда это могло иметь отношение к динамическим библиотекам и плагинам, создаваемым с помощью мелкомягкого компоновщика. Тут же GNUтое всё... Наверняка и в Линухе всё также будет работать, но на 100% в мультплатформенном приложении полагаться на такое размещение нельзя, могут быть проблемы.


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Igors от Сентябрь 03, 2014, 14:54
Замеченный Вами факт я использовал не один десяток лет - когда не было нормального способа API заставлял юзера цеплять один плагин в неск местах приложения, и дальше взаимодействие через общие переменные.

и плагин загружается из той же DLL повторно,
Нет никакой "повторной загрузки" - просто увеличивается счетчик использования dll.

(динамическая память и стек у каждого будут, разумеется, свои).
Не свои а вызывающего

По идее, все правильно, поскольку статически хранимые переменные размещаются в общем сегменте данных, который вместе с сегментом кода отображается в оперативную память процессора.
Нет. Напр если 5 процессов загрузили одну dll все используют одну копию кода и 5 копий данных. В Вындоуз можно сделать данные общими, но надо сильно постараться.

На никсовых ОС все то же самое, читайте dlopen и dlsym. Помню правил такой баг: загрузил либу - выгрузил - загрузил опять. Оба-на! Переменные остались с предыдущей загрузки :)


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Гурман от Сентябрь 03, 2014, 15:30
и плагин загружается из той же DLL повторно,
Нет никакой "повторной загрузки" - просто увеличивается счетчик использования dll.

Да, нашел уже описание. Странно, что раньше это не попадалось.

(динамическая память и стек у каждого будут, разумеется, свои).
Не свои а вызывающего

Я имел в виду - разные. Это понятно, что у приложения куча одна и стек один.

По идее, все правильно, поскольку статически хранимые переменные размещаются в общем сегменте данных, который вместе с сегментом кода отображается в оперативную память процессора.
Нет. Напр если 5 процессов загрузили одну dll все используют одну копию кода и 5 копий данных. В Вындоуз можно сделать данные общими, но надо сильно постараться.

Это если 5 разных процессов. Я имел в виду случай загрузки одной DLL несколько раз одним и тем же процессом. Статические данные вместе с кодом отобразились один раз, и как вы выше сказали, затем только увеличивается счетчик использования - не только сегмента кода, но и сегмента данных.

На никсовых ОС все то же самое, читайте dlopen и dlsym. Помню правил такой баг: загрузил либу - выгрузил - загрузил опять. Оба-на! Переменные остались с предыдущей загрузки :)

Вот это теперь для меня вполне обоснованно.

Сижу теперь и думаю, что в реальной жизни будет лучше, если по условию для каждого плагина надо иметь собственные сегменты данных - плодить копии плагинов и грузить их как отдельные, либо переделывать плагины, чтобы у них не было статических данных, все только динамические. Первый вариант проще. Но это приведет к многократной загрузке и дублированию в памяти одинакового кода, хотя он и не страшно смертельного объема, около 800 КБ на плагин, но может вырасти до 2 МБ. Максимум в реальности будет загружаться 10-20 таких плагинов. В принципе, не смертельно. Но надо еще придумать, как и когда создавать копии. Второй вариант - это переделка всего такого плагина, а он... сложный. И давно написанный и хорошо отлаженный. И статические данные там неспроста появились.


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Igors от Сентябрь 03, 2014, 17:53
Сижу теперь и думаю, что в реальной жизни будет лучше, если по условию для каждого плагина надо иметь собственные сегменты данных - плодить копии плагинов и грузить их как отдельные, либо переделывать плагины, чтобы у них не было статических данных, все только динамические.
Только динамические - необязательно, нередко что-то даже лучше иметь статичным. Но основные/рабочие параметры да, в куче и у каждого экземпляра свои. Для этого есть хороший критерий: multi-threading. Как только потребуется параллельное выполнение вся статика рухнет.

Боязнь переделок понятна - плагинов-то совсем не один, могут быть писаны давно и хз как теперь компилить. Но все же чем раньше - тем лучше. Возможный подход: на фазе инициализации плагин выделяет блок памяти где размещает рабочие данные/параметры. Адрес этого блока возвращается хосту, а тот при каждом вызове плагина опять подает ему этот же адрес.


Название: Re: Как обработать сигнал одним слотом из подключенных?
Отправлено: Гурман от Сентябрь 03, 2014, 22:17
Именно о параллельном выполнении и речь. Переделывать плагины пока не стал, хотя оно все тут же, связано зависимостями с проектом, и можно менять. Но сделал пока простой вариант - автоматически при необходимости на носителе создаются копии плагина, которые загружаются. Если копия уже есть, то просто загружается, не создается. Просто надо другое делать, всего делать вообще дофига, а переделка задержит на неопределенное время. Там в плагинах сложно, около 15000 строк... В следующей версии можно будет и переделать, если понадобится, это как отдельная задача фактически.