PDA

View Full Version : Problem with Qt plugin system



zuck
18th March 2009, 19:40
Hi, this is my first post here :)

I'm trying to realize an embedded application framework based on Qt 4.5 (Qt Embedded for Linux) similar to Qtopia/QtExtended.

I want to implement a dynamic loader for third-party applications, so I'm using the Qt plugin system. I have my application interface class:



class pkApplication
{
public:
enum { InvalidId = 0 };

public:
virtual ~pkApplication() {};

virtual unsigned int id() const = 0;
virtual bool startUp(QWidget *parent = 0) = 0;
};

Q_DECLARE_INTERFACE(pkApplication, "it.card-tech.pk.pkApplication/1.0");


Then, my base application mixin class (it implements application low-level logic and it inherits from QObject):



class pkApplicationBase : public QObject, public pkApplication
{
Q_OBJECT

private:
static unsigned int s_idCounter;
static QList<unsigned int> s_freeIds;
unsigned int m_id;

public:
pkApplicationBase();
virtual ~pkApplicationBase();

unsigned int id() const;

private:
unsigned int nextFreeId();
};


And, at the end, my concrete example application:



class ExampleApp : public pkApplicationBase
{
Q_OBJECT
Q_INTERFACES(pkApplication)

public:
ExampleApp();

bool startUp(QWidget* parent);

QDialog* w;
};


(with Q_EXPORT_PLUGIN2(exampleapp, ExampleApp) line in the source file).

This configuration doesn't work even if it respects pure interface and no-qobject-inheritance (on pkApplication) rules.

If I inherit ExampleApp directly from pkApplication everything works (obviously with some changes here and there).

Could you help me to find a solution for this problem, please? :)

fullmetalcoder
18th March 2009, 19:49
This configuration doesn't work even if it respects pure interface and no-qobject-inheritance (on pkApplication) rules.

If I inherit ExampleApp directly from pkApplication everything works (obviously with some changes here and there).
Wow, you get the exact opposite of the expected behavior :eek: There must something weird with your project layout. Oh, by the way, would you mind defining "doesn't work", "everything work" and "some changes here and there", that might help us figuring out what's wrong.


Could you help me to find a solution for this problem, please?
Maybe but a more detailed explanation is needed (project layout, test you made, expected results, actual results and such...)

fullmetalcoder
18th March 2009, 19:58
class pkApplicationBase : public QObject, public pkApplication
{
Q_OBJECT
Q_INTERFACES(pkApplication)

private:
static unsigned int s_idCounter;
static QList<unsigned int> s_freeIds;
unsigned int m_id;

public:
pkApplicationBase();
virtual ~pkApplicationBase();

unsigned int id() const;

private:
unsigned int nextFreeId();
};




class ExampleApp : public pkApplicationBase
{
Q_OBJECT

public:
ExampleApp();

bool startUp(QWidget* parent);

QDialog* w;
};


It may or may not matter but I think the above is more correct than your original code (the Q_INTERFACE macro should be placed in the first class of the inheritance chain that actually reimplement the interface).

By the way, the "pure interface" rule matters only if your plugins do not use Qt themselves (which may happen but is generally unlikely and not your case obviously), otherwise inheriting from Qt classes is fine. The real point is that your base plugin class does not bring new methods/members or you would have to put it in a shared lib and both the app and the plugins would have to link against that lib.

zuck
18th March 2009, 20:15
Wow, you get the exact opposite of the expected behavior :eek: There must something weird with your project layout.

Why? If ExampleApp inherits directly from pkApplication the situation is similar to EchoPlugin example in the Qt documentation, isn't it? :confused:



Oh, by the way, would you mind defining "doesn't work", "everything work" and "some changes here and there", that might help us figuring out what's wrong.

Maybe but a more detailed explanation is needed (project layout, test you made, expected results, actual results and such...)

Yes, sure, i'm sorry :p

I have a server application (pkServer) which inherits from QApplication. It has a loadApps() method. With this method the server should load and startup all plugins (third-party applications). Each plugin should inherits from pkApplication interface.

For "everything works" I mean that after loadApps execution, ExampleApp is correctly loaded and its main window appears on the top of pkServer "desktop" window. With the previous layout, instead, no example window appears because plugin loading fails (it returns a null pointer instead of a pkApplication pointer). This is the body of loadApps method (which is very similar to Qt documentation one and I think the problem is not here):



QList<pkApplication*> pkServer::loadApps()
{
QList<pkApplication*> loaded_apps;
QDir pluginsDir(this->applicationDirPath());

foreach (QString filename, pluginsDir.entryList(QDir::Files | QDir::NoSymLinks))
{

QPluginLoader plugin(pluginsDir.absoluteFilePath(filename));
pkApplication* app = qobject_cast<pkApplication*>(plugin.instance());
if (app)
{
app->startUp(m_desktop);
loaded_apps.append(app);
}
}

return loaded_apps;
}


and this is a simple startUp() method implementation for ExampleApp:



bool ExampleApp::startUp(QWidget* parent)
{
w = new QDialog(parent);
w->setWindowTitle("Example");
w->show();

return true;
}

fullmetalcoder
18th March 2009, 20:24
Why? If ExampleApp inherits directly from pkApplication the situation is similar to EchoPlugin example in the Qt documentation, isn't it? :confused:
Maybe "expected" wasn't the right word... "intuitive" would have been a wiser choice I guess.


plugin loading fails (it returns a null pointer instead of a pkApplication pointer).
Then I suppose my previous comment was accurate : QPluginLoader tries to cast the object to pkApplication using qobject_cast<> but as pkApplicationBase does not have the Q_INTERFACE macro this fails (qobject_cast does not work like dynamic_cast, it uses meta object data to check the validity of the cast and then "brute force" casting if the checks succeed).

zuck
19th March 2009, 00:26
Then I suppose my previous comment was accurate : QPluginLoader tries to cast the object to pkApplication using qobject_cast<> but as pkApplicationBase does not have the Q_INTERFACE macro this fails

Yes but if I move the Q_INTERFACES macro into the pkApplicationBase body nothing changes (no example window appears when i launch the server application) and the pointer is always null.

This is the error string returned by QPluginLoader:



Cannot load library [...]/libexampleapp.so: ([...]/libexampleapp.so: undefined symbol: _ZN17pkApplicationBase16staticMetaObjectE)

fullmetalcoder
19th March 2009, 12:36
Because that is a typical example of breaking the "(almost) pure virtual interface". There is an issue with your design I think.

What about something like this :


class pkApplication
{
public:
enum { InvalidId = 0 };

public:
virtual ~pkApplication() {};

virtual void setId(unsigned int id) = 0;
virtual unsigned int id() const = 0;
virtual bool startUp(QWidget *parent = 0) = 0;
};

#define PK_APPLICATION \
int m_id; \
void setId(unsigned int id) { m_id = id; } \
unsigned int id() const { return m_id; } \




class ExampleApplication : public QObject, public pkApplication
{
Q_OBJECT
Q_INTERFACES(pkApplication)

PK_APPLICATION

public:
ExampleApp();

bool startUp(QWidget* parent);

QDialog* w;

};
It should work just fine. Alternatively you may consider removing the id thingy (and using another structure like a QHash to still keep that for your app to use) from the pkApplication as the plugins probably don't need to know anything about it.

zuck
19th March 2009, 14:13
Alternatively you may consider removing the id thingy (and using another structure like a QHash to still keep that for your app to use) from the pkApplication as the plugins probably don't need to know anything about it.

Ok, for this time I can remove "Id" stuff...but if I need to share some concrete methods (which the pkServer instance needs to know too) with all applications derived from pkApplication interface? How can I do that?

Oh, by the way, thanks for your help! ;)

fullmetalcoder
19th March 2009, 21:50
if I need to share some concrete methods (which the pkServer instance needs to know too) with all applications derived from pkApplication interface? How can I do that?

it is recommended to avoid that as much as possible. Your plugin system should be designed (mostly) one-way : the application call methods from the plugin and the plugin acts on its own.
For the cases where it is really needed, there are two main ways :


passing pointers to "controllers" object (recommened method in most cases, more on this below)
putting all the common code in a shared lib. both the app and the plugins would link against it

A controller object is basically a class that allows the plugin to obtain data or to perform actions in a way that is not "predictable" by the app.

Just create another abstract class exposing methods of interest (which must be pure virtual). Then subclass that class in your app to provide actual implementation of these methods and pass a pointer to an object of your controller class to the plugins. e.g :



class pkApplicationController
{
public:
virtual ~pkApplicationController() {}

virtual QString statusMessage() const = 0;
virtual void setStatusMessage(const QString& s) = 0;
};




class pkConcreteAppController : public pkApplicationController
{
public:
QString statusMessage() const { return m_status; }
void setStatusMessage(const QString& s) { m_status = s; }
};




// somewhere in your app : (pointerToController is a pkConcreteAppController*)
plugin->someMethod(pointerToController);




// in a plugin
void SomePlugin::someMethod(pkApplicationController *c)
{
if ( !happy )
c->setStatusMessage("Always look at the bright side of life.");
}


I hope this helps.

zuck
19th March 2009, 22:18
Yes, this is a solution.

What about using QLibrary instead of QPluginLoader? With QLibrary can I have an abstract base class (no pure virtual methods only) as "parent" for all derived applications?

fullmetalcoder
20th March 2009, 00:54
using QLibrary instead of QPluginLoader offers a single avantage : more control. QPluginLoader actually uses a QLibrary under the hood so the same restrictions apply. Besides, using QLibrary implies recreating what QPluginLoader provides (you cannot create objects directly from shared libs, you have to export a "creator" functions which must have a name known to the plugin loading code, must be mangled properly, ...)

Just to clarify about the requirements on a "base class" for plugins (provided you don't use a "common shared lib" approach) :


such a class does not need to be pure virtual (aka abstract) stricto sensu (it can inherit non-abstract classes for instance)
it must however be pure virtual in a "practical sense", which basically means that the only methods it brings (on its own, see previous remark) must be pure virtual and it cannot have member variables of its own

An important consequence is that such a class can inherit QObject for instance but then the Q_OBJECT macro must be omitted as it adds members/methods used by the meta object system.

zuck
20th March 2009, 01:22
So I can write the inverse of original situation:



class pkApplicationBase : public QObject
{
// The id logic here (without Q_OBJECT macro).
};

class pkApplication : public pkApplicationBase
{
// abstract interface here.
};


Is it correct?

fullmetalcoder
20th March 2009, 13:06
No. You can afford to use "semi-abstract" classes as base class for plugins because both your app and the plugin links to Qt libs dynamically (or any other shared lib providing classes that you could be tempted to use as ancestors of your plugin base class).

Any class that brings at least one method/member variable of its very own (i.e not through inheritance of a class defined in a common shared lib) is definitely unreachable from plugins (unless, again, you move it to a common shared lib).

Please not that the shared is equally important as the common. You may manage to get it working in some cases when compiling a common lib statically but it is extremely likely to fail miserably or a reason or another in a vast majority of cases.

In any case you're safer using only (totally) pure virtual classes in your plugin system and, if needed, a bit of meta-object magic here and there.

zuck
20th March 2009, 16:44
So the correct procedure is:

1) Put pkApplication and pkApplicationBase in a shared lib.
2) Link ExampleApp and pkServer modules to the shared lib.

...Right?