PDA

View Full Version : Plugin implementation question



JPNaude
26th August 2008, 11:14
Hi

I am stuck while trying to implement an plugin-aware application. Mainly because I'm not sure it what I want to do is possible: I have a MDI area in my main window and I want to populate it with widgets obtained through a plugin interface. I've studied the plugin examples in the Qt documentation as well as the plugin interface described in "C++ GUI Programming with Qt" and they all differ from what I need because the plugins all return a object known to Qt, for example a QPixmap or a QPen or something like that. In my case, it needs to return a class that is defined by the creator of the plugin. I'll try to explain my problem in as much detail as possible, and there is probably an easy answer. Some code might make it clearer:

In my application, I have the following plugin interface:



class viewerInterface {
public:
virtual ~viewerInterface() { }
virtual QStringList views() const = 0;
virtual QWidget * loadViewer(const QString &view_type, QString dbTable, QString dbFile) = 0;
};
Q_DECLARE_INTERFACE(viewerInterface,"com.software_name.app_name.viewerInterface/0.1")


And an intended plugin for this interface is shown below:



class varMatrixReal : public QWidget, public Ui_varMatrixBase, public viewerInterface {
Q_OBJECT
Q_INTERFACES(viewerInterface)
public:
varMatrixReal(QWidget *parent = 0);
void setupVUI(QString, QString); // Setup viewer user interface
QStringList views() const;
QWidget * loadViewer(const QString &view_type, QString dbTable, QString dbFile);

private slots:
void Close();
bool createDBConnection();

private:
// Variables
QSqlTableModel *dataModel;
QSqlDatabase dbProject;

public:
// Details received from application side
QString dbTable;
QString dbFilename;

};

#endif // !_VARMATRIXREAL_H_


where the classes it inherits from are the following:
QWidget - Needed since it will be a widget displayed in a MDI Area
Ui_varMatrixBase - GUI base class created with Qt designer
viewerInterface - The abstract interface base class

Now with this background the question is the following: When I want to use this plugin in my application, how is it possible to cast a pointer to the varMatrixReal class if the main application does not have any knowledge of the plugin except for the interface (or does it?).

The first step will be something like this:


QPluginLoader loader(pluginDir.absoluteFilePath(fileName));
if (viewerInterface*interface = qobject_cast<viewerInterface*>(loader.instance()))
interfaces.append(interface);


But after this, how is it possible to use the functions of the derived classes and not only the interface base class? I mean, to do this you should probably qobject_cast the interface pointer to a varMatrixReal class and add such an instance to the MDI Area, however how do I get the application to have knowledge of the varMatrixReal class? Or the more generic case where the plugin defined the needed derived class and the application does not have knowledge of this, in this case varMatrixReal.

I have the feeling it should be possible because the plugin is a library which contains the class definition in any case. Do I only need to link to it in the .pro file perhaps?

Thanks in advance, I hope the question is clear enough.
Jaco

wysota
26th August 2008, 11:23
The widget should not inherit the plugin interface. The plugin should be a separate object inheriting QObject and the plugin interface and from its loadViewer() method you should return a new widget like varMatricReal.

JPNaude
26th August 2008, 11:50
Ahhh, so the plugin and widget should be seperate. I think that makes sense and it would make things a little bit easier. Thanks!

So the application can just add a new subwindow to the MDI area using something like the code below, without the main application needing to know the definition of the varMatrixReal class?



mdiArea->addSubWindow(interface->loadViewer("Real Matrix,"table_name",active_db_file));

JPNaude
26th August 2008, 11:54
Reading through my last post again, that is not going to work... Is it?

wysota
26th August 2008, 12:00
It should work provided that the plugin never returns a null pointer from loadViewer and interface contains an instance of the plugin.

JPNaude
27th August 2008, 09:50
I've tried your suggestions and I am trying to understand why it would work. I've separated the plugin and the QWidget now and my loadViewer function in the plugin (matrixViewers) returns a matrixReal instance now as shown below:



matrixReal* matrixViewers::loadViewer(const QString &view_type, QString dbTable, QString dbFile) {
if (view_type == "Real") {
matrixReal* newViewer = new matrixReal;
newViewer->setupVUI(dbTable,dbFile);
return newViewer;
} else {
// Create a default window here which says the plugin failed to load
}
}


This makes sense but the loadViewer function is a virtual function in the interface base class. Now the question: How is the interface base class going to know anything about the matrixReal class? Isn't the main program using this interface without knowledge of the classes inside the plugin (matrixReal for example)?

The viewerInterface is shown below:



class viewerInterface {
public:
virtual ~viewerInterface() { }
virtual QStringList views() const = 0;
virtual matrixReal* loadViewer(const QString &view_type, QString dbTable, QString dbFile) = 0;
};
Q_DECLARE_INTERFACE(viewerInterface,"com.software_name.app-name.viewerInterface/0.1")


Is it possible to do it in this way?

wysota
27th August 2008, 12:56
How is the interface base class going to know anything about the matrixReal class?
Why should it know anything? It knows that it implements the interface and this should be enough.


Isn't the main program using this interface without knowledge of the classes inside the plugin (matrixReal for example)?

Yes, that's correct.

The viewerInterface is shown below:


Is it possible to do it in this way?
If your loadViewer() returns only subclasses of matrixReal objects than yes.

JPNaude
27th August 2008, 14:18
Thanks again for the reply. My problem is that the interface class is used when compiling the plugin library as well as compiling the main application. In both the return type of loadViewer() need to be specified. For the plugin it makes sense to use the return type of matrixReal as shown in my previous posts.

However for the main program, it can't use matrixReal because it does not know anything about it. This is what I don't understand... Should I use QWidget instead in the interface used to build the main program? I would think that the interface classes should be exactly the same.

wysota
27th August 2008, 14:24
However for the main program, it can't use matrixReal because it does not know anything about it.

So the interface should return a QWidget pointer.


class Iface {
public:
QWidget *getWidget() = 0;
};

class Implementation : public QObject, Iface {
Q_OBJECT
public:
QWidget *getWidget() { return new matrixReal; }
};

JPNaude
27th August 2008, 14:55
Ok that helps, thanks!. I've tried this before but it did not work so I thought there was an error on my side. So I assume when you return a matrixReal pointer to a QWidget pointer it does some sort of reinterpret_cast<QWidget *> automatically.

I'm still not 100% sure why the main program can use the QWidget pointer as a matrixReal pointer without doing a qobject_cast<matrixReal*> on it. However I believe you.

For the moment, I am stuck where the plugin dll return a 0x1 pointer from the loadViewer() function and this pointer is useless since it bombs the program when I use it. So, I'm not sure if this is due to the way I've done my plugin or due to something else in the main program where I use the pointer.

wysota
27th August 2008, 15:18
've tried this before but it did not work so I thought there was an error on my side.
Maybe you forgot to include matrixreal.h.


So I assume when you return a matrixReal pointer to a QWidget pointer it does some sort of reinterpret_cast<QWidget *> automatically.
Not really. As matrixReal inherits QWidget, you can always cast to the base class, so this cast is purely implicit and based on C++ rules.


I'm still not 100% sure why the main program can use the QWidget pointer as a matrixReal pointer without doing a qobject_cast<matrixReal*> on it. However I believe you.

This is one of C++ (or rather OO-programming) paradigms.


For the moment, I am stuck where the plugin dll return a 0x1 pointer from the loadViewer() function and this pointer is useless since it bombs the program when I use it. So, I'm not sure if this is due to the way I've done my plugin or due to something else in the main program where I use the pointer.
0x1 is... 1. So you must be returning an improper object (not allocated with new).

JPNaude
27th August 2008, 16:31
Thanks alot for the help! I've checked and my return pointer is initialized correctly, so something else is wrong. I would need to debug it but ran out of time now. The only trace I can find at the moment is a bunch of disassembly messages like this:

0x7c90eaf0 <ntdll!LdrDisableThreadCalloutsForDll+4>: mov (%esp),%ebx

I've built both the library and the main app in debug mode. I'm calling the interface like this:



foreach (viewerInterface *interface, viewerInterfaces) {
QWidget * viewer = new QWidget(this);
viewer = interface->loadViewer("Real",dropEvent->mimeData()->text(),active_db_file);
try {
viewer->show();
}


And my return function in the library looks something like this, just to test it:


QWidget* matrixViewers::loadViewer(const QString &view_type, QString dbTable, QString dbFile) {
/*if (view_type == "Real") {
matrixReal* newViewer = new matrixReal(0);
newViewer->setupVUI(dbTable,dbFile);
return newViewer;
} else {*/
// Create a default window here which says the plugin failed to load
QLabel *label = new QLabel(tr("Viewer loading failed..."));
QWidget *failWindow = new QWidget;
QHBoxLayout *topLeftLayout = new QHBoxLayout;
topLeftLayout->addWidget(label);
failWindow->setLayout(topLeftLayout);
failWindow->show();
return failWindow;
//}
}


My knowledge of library usage is limited so I need to spend some time on it probably, if there is nothing obvious wrong with the above...

Thanks again...

wysota
27th August 2008, 21:24
Instead of returning a "fail widget", return 0 and let the main application handle the fails.