Verwendung von Signals in Pluginklassen

Alles rund um die Programmierung mit Qt
hel800
Beiträge: 16
Registriert: 5. Dezember 2007 17:54

Verwendung von Signals in Pluginklassen

Beitrag von hel800 »

Hallo zusammen,

ich habe folgende Frage: Ich möchte meine Anwendung durch Plugins erweiterbar machen. Das hab ich auch schon geschafft, indem ich mich an dem QT-Example Plug and Paint orientiert habe.

Dazu habe ich eine Interfaceklasse (DSGPluginInterface) s.u., die von meiner Hauptklasse im Plugin-Project vererbt wird. Das Interface erbt von keiner anderen Klasse (wie im Plug and Paint auch). Das funktioniert alles soweit einwandfrei.

Nun möchte ich aber, dass die Plugins signals aussenden können, die ich in meiner Anwendung mit Funktionen verknüpfen kann. Das geht nach momentanem Stand nicht. Wie muss ich da vorgehen? Ich habe schon das Interface von QObject erben lassen, um Signals im Interface zu definieren. Dann kann ich aber meine Plugin-Klasse nicht mehr von QObject erben lassen und es kommt dort zu "undefined references", weil er z.B. das Macro Q_EXPORT_PLUGIN2 nicht mehr findet. Habt ihr da eine Idee? Das wär spitze. Hier noch eine Skizze der beiden Klassen, so wie sie momentan angelegt sind (ohne Signals):

Interface:

Code: Alles auswählen

#ifndef DSGPLUGININTERFACE_H
#define DSGPLUGININTERFACE_H

#include <QString>
#include <dsimage.h>

class DSGPluginInterface
{
public:
    virtual ~DSGPluginInterface() { delete outputImage; }
    virtual QString getPluginName() = 0;

    virtual void createDialogUI() = 0;
    virtual void createWidgetUI() {}
    virtual bool start() = 0;

    virtual dynaSAUR::DSImage * getOutputImage() { return this->outputImage; }

    virtual QString getLastError() { return this->lastError; }
    virtual bool supportsWidgetUI() { return false; }

protected:
    dynaSAUR::DSImage * inputImage;
    dynaSAUR::DSImage * outputImage;

    QString lastError;

    virtual void makeError(const QString &st) { this->lastError = st; }
};

Q_DECLARE_INTERFACE(DSGPluginInterface, "com.DSGExplorer2.Plugin.Interface/1.0")

#endif // DSGPLUGININTERFACE_H
Hier das Beispiel für ein Plugin, welches das Interface nutzt:

Code: Alles auswählen

#ifndef TESTPLUGIN_H
#define TESTPLUGIN_H

#include <QObject>
#include <QString>
#include <QtPlugin>
#include "dsgplugininterface.h"

class TestPlugin : public QObject, public DSGPluginInterface
{
    Q_OBJECT
    Q_INTERFACES(DSGPluginInterface)
public:
    QString getPluginName();

    void createDialogUI();
    void createWidgetUI();
    bool start();

    virtual bool supportsWidgetUI() { return true; }
};

#endif // TESTPLUGIN_H
Vielen Dank im Voraus für Eure Ideen.
-Sebastian
archer
Beiträge: 306
Registriert: 2. Februar 2006 09:56

Beitrag von archer »

Du mußt in dem Plugin eine Methode implementieren der du einen Slot von Außen übergibst. Dann kannst du innerhalb des Plugins das connect machen.
hel800
Beiträge: 16
Registriert: 5. Dezember 2007 17:54

Beitrag von hel800 »

Hey, vielen Dank für die Idee. Funktioniert wunderbar! Manchmal sieht man den Wald vor lauter Bäumen nicht :D
AuE
Beiträge: 918
Registriert: 5. August 2008 10:58

Beitrag von AuE »

mhhh kannst das bitte mal posten? Weil meiner meinung nach muss doch dann das plugin was vom "Ladenden" Programm wissen oder?
hel800
Beiträge: 16
Registriert: 5. Dezember 2007 17:54

Beitrag von hel800 »

Also, im Prinzip habe ich das Interface nur um eine Methode und zwei Member erweitert, die receiver und slot speichern. Die "aufrufende" Klasse muss nicht bekannt sein, da SLOT nicht im Plugin sondern vorher definiert wird.

Interface:

Code: Alles auswählen

#ifndef DSGPLUGININTERFACE_H
#define DSGPLUGININTERFACE_H

#include <QString>
#include <dsimage.h>

class DSGPluginInterface
{
public:
    virtual ~DSGPluginInterface() { delete outputImage; }
    virtual QString getPluginName() = 0;

    virtual void createDialogUI() = 0;
    virtual void createWidgetUI() {}
    virtual bool start() = 0;

    virtual dynaSAUR::DSImage * getOutputImage() { return this->outputImage; }

    virtual QString getLastError() { return this->lastError; }
    virtual bool supportsWidgetUI() { return false; }

    virtual void setReceiverAndSlot(const QObject * receiver, const char * method)
                                                              {
                                                                this->signalReceiver = receiver;
                                                                this->signalMethod = method;
                                                              }

protected:
    dynaSAUR::DSImage * inputImage;
    dynaSAUR::DSImage * outputImage;

    const QObject * signalReceiver;
    const char * signalMethod;

    QString lastError;

    virtual void makeError(const QString &st) { this->lastError = st; }
};

Q_DECLARE_INTERFACE(DSGPluginInterface, "com.DSGExplorer2.Plugin.Interface/1.0")

#endif // DSGPLUGININTERFACE_H
Die Methode setReceiverAndSlot kann dann in der Anwendung so aufgerufen werden:

Code: Alles auswählen

plug->setReceiverAndSlot(this, SLOT(update()));
Im Plugin selbst kann man dann an beliebiger Stelle den connect machen. In der Klassendefinition müssen keine Änderungen vorgenommen werden.

Code: Alles auswählen

connect(pushButton, SIGNAL(clicked()), this->signalReceiver, this->signalMethod);
AuE
Beiträge: 918
Registriert: 5. August 2008 10:58

Beitrag von AuE »

Also ist dein Plugin quasi ne Funktionssammlung und die UI ist im MainWindow drin (der Pushbutton ist im Hauptprogramm und die Funktion die aufgerufen wird im Plugin?)
hel800
Beiträge: 16
Registriert: 5. Dezember 2007 17:54

Beitrag von hel800 »

Nene, der PushButton ist im Plugin definiert und steht nur stellvertretend für jedes SIGNAL was im Plugin entstehen kann. Die Funktion wird ja aus dem Hauptprogramm mit setReceiverAndSlot übergeben und ist auch da definiert. Dem Plugin wird bloß der Zugriff ermöglicht.

Ich nutze meine Plugins um beliebige Änderungen (bspw. Filter) an Bildern vorzunehmen. Jedes Plugin bekommt ein inputImage bereitgestellt und muss dafür ein outputImage anlegen und widerum bereitstellen. Außerdem kann das Plugin einen Properties Dialog verwalten und ein zusätzliches ausgabe Widget zur Anzeige von zusatzinfos aber auch der möglichkeit das Plugin neu zu berechnen usw...

Ich habe zwecks besserer Übersicht nicht relevante Funktionen oben rausgenommen. Daher fehlen zB die UI-Sacchen, sorry wenn das zu Verwirrungen geführt hat.

Ich hoffe es ist etwas klarer geworden ;-)
AuE
Beiträge: 918
Registriert: 5. August 2008 10:58

Beitrag von AuE »

Jau thx..... is nen guter Ansatz den ich auch mal weiter verfolgen werde!

THX!
AuE
Beiträge: 918
Registriert: 5. August 2008 10:58

Beitrag von AuE »

BTW:

Ich habe das bisher immer anders gelöst ;-)

Ich habe den Plugin einen privaten member hinzugefügt

Code: Alles auswählen

class GlobalMainInterface // BASECLASS FROM WICH ALL THE OTHER ARE DERIVED
{
public:
	virtual QWidget *createOptionWidget(QWidget *parent) = 0;
	virtual QString getName()=0;
	virtual QList<QAction *> getSystrayActions()=0;

private:
	CInternalisation m_internationalisation;

	friend class MainWindow;
};

Wobei die CInternalisation von QObject abgeleitet ist und den Slot für das retranlate bereitstellt.
Dann habe ich die so verbunden

Code: Alles auswählen

  connect(this, SIGNAL(sigLanguageChange(QString)), &plug->m_internationalisation, SLOT(on_languagChange(QString)));
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

@hel800:
Ich hatte bisher noch nie Probleme mit Plugins und Signals.. bei mir erbt das Interface immer von QObject... Gerade in der Qt-Umgebung ist es sehr elegant, wenn zum Interface eines Plugins aus Signals und Slots gehören.
Wenn dann das Design-Problem besteht, dass das Plugin auch noch von einer anderen QObject-abgeleiteten Klasse erben soll, lässt sich das auch mit Aggregation lösen (das Plugin IST also kein QLineEdit, sondern es HAT ein QLineEdit..). Siehe dazu auch Proxy-Pattern...

@AuE
wozu denn das "friend"? Warum nicht eine get-Methode?

Code: Alles auswählen

class GlobalMainInterface
{
public:
   ..
   QObject *internalisation() const;
   ...
};
..
..
connect(this, SIGNAL(sigLanguageChange(QString)), plug->internalisation(), SLOT(on_languagChange(QString)));
// edit: Proxy-Hinweis
AuE
Beiträge: 918
Registriert: 5. August 2008 10:58

Beitrag von AuE »

Das friend -> weil nur die eine Klasse drauf zugreifen soll/darf.
Eigentlich wollte ich das noch durch ne friend function ersetzen .... gut das du mich wieder drauf gebracht hast.
hel800
Beiträge: 16
Registriert: 5. Dezember 2007 17:54

Beitrag von hel800 »

@solarix: Dein Beitrag hat mich nochmal veranlasst es doch ohne diese Methode zu machen, die den SLOT bereitstellt, sondern wie von dir beschrieben mit SIGNALS, die direkt vom Plugin ausgehen:

Dazu habe ich meinen Code von oben wie folgt abgeändernt, leider treten noch Probleme auf :(. Ich habe das ganze erstmal ohne SIGNAL defininert. Will es erstmal so zum laufen bringen:

Interface:

Code: Alles auswählen

#ifndef DSGPLUGININTERFACE_H
#define DSGPLUGININTERFACE_H

#include <QString>
#include <dsimage.h>

class DSGPluginInterface : public QObject
{
    Q_OBJECT
public:
    virtual ~DSGPluginInterface() { delete outputImage; }
    virtual QString getPluginName() = 0;

    virtual void createDialogUI() = 0;
    virtual void createWidgetUI() {}
    virtual bool start() = 0;

    virtual dynaSAUR::DSImage * getOutputImage() { return this->outputImage; }

    virtual QString getLastError() { return this->lastError; }
    virtual bool supportsWidgetUI() { return false; }

protected:
    dynaSAUR::DSImage * inputImage;
    dynaSAUR::DSImage * outputImage;

    QString lastError;

    virtual void makeError(const QString &st) { this->lastError = st; }
};

Q_DECLARE_INTERFACE(DSGPluginInterface, "com.DSGExplorer2.Plugin.Interface/1.0")

#endif // DSGPLUGININTERFACE_H
Und das Bsp für ein Plugin:

Code: Alles auswählen

#ifndef TESTPLUGIN_H
#define TESTPLUGIN_H

#include <QObject>
#include <QString>
#include <QtPlugin>
#include "dsgplugininterface.h"

class TestPlugin : public DSGPluginInterface
{
    Q_INTERFACES(DSGPluginInterface)
public:
    QString getPluginName();

    void createDialogUI();
    void createWidgetUI();
    bool start();

    virtual bool supportsWidgetUI() { return true; }
};

#endif // TESTPLUGIN_H
Beim Erstellen des Plugins kommt es zu "undefined references to `vtable for DSGPluginInterface'. Ich habe auch schon versucht im Plugin das Q_OBJECT Macro aufzunehmen, ändert aber nix daran. Hast du noch einen Tip, woran das liegen könnte? Oder mache ich irgendwas falsch? Wär echt super, wenn ich das doch mit den SIGNALs hinbekommen würde.
Vielen Dank schonmal!
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Du musst im .pro das DSGPluginInterface.h zu den HEADERS hinzufügen.
hel800
Beiträge: 16
Registriert: 5. Dezember 2007 17:54

Beitrag von hel800 »

ist bereits dabei, daran liegt es leider nicht :-/
Christian81
Beiträge: 7319
Registriert: 26. August 2004 14:11
Wohnort: Bremen
Kontaktdaten:

Beitrag von Christian81 »

[quote="hel800"]ist bereits dabei, daran liegt es leider nicht :-/[/quote
Doch, unter Garantie. Wenn es in HEADERS aufgeführt ist erzeugt qmake ein Kommando damit die moc-Datei erzeugt und kompiliert wird...
MfG Christian

'Funktioniert nicht' ist keine Fehlerbeschreibung
Antworten