Plugins: Difference between revisions
(Cleanup) |
(Fix link) |
||
(One intermediate revision by one other user not shown) | |||
Line 4: | Line 4: | ||
''Written by Girish Ramakrishnan, ForwardBias Technologies'' | ''Written by Girish Ramakrishnan, ForwardBias Technologies'' | ||
== QLibrary == | ==QLibrary== | ||
{{DocLink|QLibrary}} resolves 'C' symbols in DLLs and shared objects using the standard platform API. dlopen()/dlsym() is used on Unix, GetProcAddress on Windows. | {{DocLink|QLibrary}} resolves 'C' symbols in DLLs and shared objects using the standard platform API. dlopen()/dlsym() is used on Unix, GetProcAddress on Windows. | ||
== Qt Plugins == | ==Qt Plugins== | ||
[[Basics of Plugins]] describes the basics concepts of writing plugins. | [[Basics Of Plugins|Basics of Plugins]] describes the basics concepts of writing plugins. | ||
'''Qt's plugin mechanism is meant for plugins that use Qt'''. It provides a bunch of macros that helps us create the C-function that generates the plugin object and also generates meta information (through moc) as to whether an object implements an interface. Since, Qt plugins use Qt, it also verifies if the plugin was compiled with a compatible version of Qt as the application itself. | '''Qt's plugin mechanism is meant for plugins that use Qt'''. It provides a bunch of macros that helps us create the C-function that generates the plugin object and also generates meta information (through moc) as to whether an object implements an interface. Since, Qt plugins use Qt, it also verifies if the plugin was compiled with a compatible version of Qt as the application itself. | ||
Consider the following basic Qt plugin code for the purpose of this article: | Consider the following basic Qt plugin code for the purpose of this article: | ||
// Hammer in hammer.h (our Hammer plugin) | <nowiki> | ||
#include "toolinterface.h" | // ToolInterface in toolinterface.h | ||
class Hammer : public QObject, public ToolInterface | class ToolInterface | ||
{ | { | ||
public: | |||
virtual QString toolName() const = 0; | |||
public: | }; | ||
Q_DECLARE_INTERFACE(ToolInterface, "in.forwardbias.tool/1.0"); | |||
}; | |||
Q_EXPORT_PLUGIN2(hammer, Hammer); | // Hammer in hammer.h (our Hammer plugin) | ||
</ | #include "toolinterface.h" | ||
class Hammer : public QObject, public ToolInterface | |||
{ | |||
Q_OBJECT | |||
Q_INTERFACES(ToolInterface) | |||
public: | |||
QString toolName() const { return "hammer"; } | |||
}; | |||
Q_EXPORT_PLUGIN2(hammer, Hammer); | |||
</nowiki> | |||
The purpose of Q_DECLARE_INTERFACE and Q_INTERFACES is explained in the upcoming sections, | The purpose of Q_DECLARE_INTERFACE and Q_INTERFACES is explained in the upcoming sections, | ||
== Q_INTERFACES == | ==Q_INTERFACES== | ||
When moc runs on the hammer.h code, it inspects Q_INTERFACES. It generates code for a function called qt_metacall - void *Hammer::qt_metacast(const char *iname). This goal of this 'casting' function is to return a pointer to an interface depending on iname. moc also verifies whether the interface names that you have put inside Q_INTERFACES have indeed been declared. It can do this by inspecting the header files and looking for a Q_DECLARE_INTERFACE. In our case, there is a Q_DECLARE_INTERFACE inside toolinterface.h. | When moc runs on the hammer.h code, it inspects Q_INTERFACES. It generates code for a function called qt_metacall - void *Hammer::qt_metacast(const char *iname). This goal of this 'casting' function is to return a pointer to an interface depending on iname. moc also verifies whether the interface names that you have put inside Q_INTERFACES have indeed been declared. It can do this by inspecting the header files and looking for a Q_DECLARE_INTERFACE. In our case, there is a Q_DECLARE_INTERFACE inside toolinterface.h. | ||
Rough pseudo code: | Rough pseudo code: | ||
< | <nowiki> | ||
// in moc_hammer.cpp | // in moc_hammer.cpp | ||
void *Hammer::qt_metacast(const char *iname) | void *Hammer::qt_metacast(const char *iname) | ||
{ | { | ||
if (strcmp(iname, "Hammer") 0) return this; | |||
if (strcmp(iname, "ToolInterface") 0) return static_cast<ToolInterface*>(this); | |||
// .. additional comparisons if you had more than one interface in Hammer | |||
if (strcmp(iname, "in.forwardbias.tool/1.0") == 0) return static_cast<ToolInterface*>(this); | |||
// also responds to the string in Q_DECLARE_INTERFACE | |||
} | } | ||
</ | </nowiki> | ||
One of the caveats to be aware of is that moc is not aware of interface inheritance. For example, if ToolInterface derived from GenericInterface, it should be possible to qt_metacast to GenericInterface. moc is no C++ parser and hence it would not add GenericInterface to the qt_metacast code that it generated above. A workaround is to write Q_INTERFACES(ToolInterface:GenericInterface) in hammer.h. The ":" indicates derives. | One of the caveats to be aware of is that moc is not aware of interface inheritance. For example, if ToolInterface derived from GenericInterface, it should be possible to qt_metacast to GenericInterface. moc is no C++ parser and hence it would not add GenericInterface to the qt_metacast code that it generated above. A workaround is to write Q_INTERFACES(ToolInterface:GenericInterface) in hammer.h. The ":" indicates derives. | ||
== Q_DECLARE_INTERFACE == | ==Q_DECLARE_INTERFACE== | ||
Q_DECLARE_INTERFACE is a macro that defines helper functions that make qobject_cast<Tool*>(hammer) return a Tool pointer. qobject_cast is just a template function and you can think of Q_DECLARE_INTERFACE providing a template specialization for the interface. The macro itself just expands as a call to qt_metacast that moc generated above. So, Q_DECLARE_INTEFACE defines qobject_cast<Tool *>(object) which is a template specialization that translates to object->qt_metacall("in.forwardbias.tool/1.0"); | Q_DECLARE_INTERFACE is a macro that defines helper functions that make qobject_cast<Tool*>(hammer) return a Tool pointer. qobject_cast is just a template function and you can think of Q_DECLARE_INTERFACE providing a template specialization for the interface. The macro itself just expands as a call to qt_metacast that moc generated above. So, Q_DECLARE_INTEFACE defines qobject_cast<Tool *>(object) which is a template specialization that translates to object->qt_metacall("in.forwardbias.tool/1.0"); | ||
== Q_EXPORT_PLUGIN2 == | ==Q_EXPORT_PLUGIN2== | ||
This is the C-function that gets exported in the shared object. It goes something like: | This is the C-function that gets exported in the shared object. It goes something like: | ||
< | |||
// The pluginName isn't used (see the section static plugins for it's purpose) | <nowiki> | ||
#define Q_EXPORT_PLUGIN2(pluginName, PluginClass) | // The pluginName isn't used (see the section static plugins for it's purpose) | ||
extern "C" PluginClass *qt_plugin_instance() { return new pluginClass; } | #define Q_EXPORT_PLUGIN2(pluginName, PluginClass) | ||
extern "C" const char *qt_plugin_verification_data() { | extern "C" PluginClass *qt_plugin_instance() { return new pluginClass; } | ||
extern "C" const char *qt_plugin_verification_data() { | |||
} | return "pattern=QT_PLUGIN_VERIFICATION_DATA\nversion=4.5.3\ndebug=false\nbuildkey=x86_64 linux g++-4 full-config"; | ||
</ | } | ||
</nowiki> | |||
Note: qt_plugin_instance actually uses a singleton but simplified above for easier understanding | Note: qt_plugin_instance actually uses a singleton but simplified above for easier understanding | ||
Line 77: | Line 79: | ||
Q_IMPORT_PLUGIN2 defines the C-function that creates an instance of the plugin and also contains an additional C-function that returns the Qt config that the plugin was compiled with. | Q_IMPORT_PLUGIN2 defines the C-function that creates an instance of the plugin and also contains an additional C-function that returns the Qt config that the plugin was compiled with. | ||
== QPluginLoader == | ==QPluginLoader== | ||
QPluginLoader verifies if a plugin is compatible with the application using the verification data (see example above) that was embedded as part of Q_EXPORT_PLUGIN2. One interesting aspect under unix is that instead of loading the library and resolving the function, Qt will mmap the library and do a string search instead (from the end of the file i.e in reverse). The reason for this seems to be that it is apparently faster that resolving and one can avoid loading incompatible plugins. | QPluginLoader verifies if a plugin is compatible with the application using the verification data (see example above) that was embedded as part of Q_EXPORT_PLUGIN2. One interesting aspect under unix is that instead of loading the library and resolving the function, Qt will mmap the library and do a string search instead (from the end of the file i.e in reverse). The reason for this seems to be that it is apparently faster that resolving and one can avoid loading incompatible plugins. | ||
== Standard Qt plugins == | ==Standard Qt plugins== | ||
Various parts of Qt can be extended using plugins- codecs, styles, font engines etc. Given a plugin, one will have to cast it to all supported interfaces to determine what the plugin actually implements. To avoid this overhead, Qt defines standard paths into which plugins should be places. For examples, style plugins must be places under plugins/styles/ | Various parts of Qt can be extended using plugins- codecs, styles, font engines etc. Given a plugin, one will have to cast it to all supported interfaces to determine what the plugin actually implements. To avoid this overhead, Qt defines standard paths into which plugins should be places. For examples, style plugins must be places under plugins/styles/ | ||
== Static plugins == | ==Static plugins== | ||
When Qt is built in static mode, plugins also have to be static. Why? Since Qt is static, it simply cannot load plugins that link to Qt dynamically. The only way is to create static libraries out of all the plugins and link all the static libraries in the target program. | When Qt is built in static mode, plugins also have to be static. Why? Since Qt is static, it simply cannot load plugins that link to Qt dynamically. The only way is to create static libraries out of all the plugins and link all the static libraries in the target program. | ||
Line 96: | Line 98: | ||
2. The plugins itself are static libraries. When our app is linked, we need to link with those static libraries. This is achieved using QTPLUGIN += pluginName in the .pro file (this simply adds -l<plugin>.a to the linker line). | 2. The plugins itself are static libraries. When our app is linked, we need to link with those static libraries. This is achieved using QTPLUGIN += pluginName in the .pro file (this simply adds -l<plugin>.a to the linker line). | ||
== FAQ == | ==FAQ== | ||
1. Can multiple plugins reside in single DLL/.so? You can host multiple plugins of the '''same''' type in one DLL but you cannot host different type plugins in a single .so. IOW, you can have two image plugins in a .so but you cannot have a font engine plugin and an image plugin in a single .so (they have to be separate .so/dlls) | 1. Can multiple plugins reside in single DLL/.so? You can host multiple plugins of the '''same''' type in one DLL but you cannot host different type plugins in a single .so. IOW, you can have two image plugins in a .so but you cannot have a font engine plugin and an image plugin in a single .so (they have to be separate .so/dlls) |
Latest revision as of 20:33, 20 July 2021
Written by Girish Ramakrishnan, ForwardBias Technologies
QLibrary
QLibrary resolves 'C' symbols in DLLs and shared objects using the standard platform API. dlopen()/dlsym() is used on Unix, GetProcAddress on Windows.
Qt Plugins
Basics of Plugins describes the basics concepts of writing plugins.
Qt's plugin mechanism is meant for plugins that use Qt. It provides a bunch of macros that helps us create the C-function that generates the plugin object and also generates meta information (through moc) as to whether an object implements an interface. Since, Qt plugins use Qt, it also verifies if the plugin was compiled with a compatible version of Qt as the application itself.
Consider the following basic Qt plugin code for the purpose of this article:
// ToolInterface in toolinterface.h class ToolInterface { public: virtual QString toolName() const = 0; }; Q_DECLARE_INTERFACE(ToolInterface, "in.forwardbias.tool/1.0"); // Hammer in hammer.h (our Hammer plugin) #include "toolinterface.h" class Hammer : public QObject, public ToolInterface { Q_OBJECT Q_INTERFACES(ToolInterface) public: QString toolName() const { return "hammer"; } }; Q_EXPORT_PLUGIN2(hammer, Hammer);
The purpose of Q_DECLARE_INTERFACE and Q_INTERFACES is explained in the upcoming sections,
Q_INTERFACES
When moc runs on the hammer.h code, it inspects Q_INTERFACES. It generates code for a function called qt_metacall - void *Hammer::qt_metacast(const char *iname). This goal of this 'casting' function is to return a pointer to an interface depending on iname. moc also verifies whether the interface names that you have put inside Q_INTERFACES have indeed been declared. It can do this by inspecting the header files and looking for a Q_DECLARE_INTERFACE. In our case, there is a Q_DECLARE_INTERFACE inside toolinterface.h.
Rough pseudo code:
// in moc_hammer.cpp void *Hammer::qt_metacast(const char *iname) { if (strcmp(iname, "Hammer") 0) return this; if (strcmp(iname, "ToolInterface") 0) return static_cast<ToolInterface*>(this); // .. additional comparisons if you had more than one interface in Hammer if (strcmp(iname, "in.forwardbias.tool/1.0") == 0) return static_cast<ToolInterface*>(this); // also responds to the string in Q_DECLARE_INTERFACE }
One of the caveats to be aware of is that moc is not aware of interface inheritance. For example, if ToolInterface derived from GenericInterface, it should be possible to qt_metacast to GenericInterface. moc is no C++ parser and hence it would not add GenericInterface to the qt_metacast code that it generated above. A workaround is to write Q_INTERFACES(ToolInterface:GenericInterface) in hammer.h. The ":" indicates derives.
Q_DECLARE_INTERFACE
Q_DECLARE_INTERFACE is a macro that defines helper functions that make qobject_cast<Tool*>(hammer) return a Tool pointer. qobject_cast is just a template function and you can think of Q_DECLARE_INTERFACE providing a template specialization for the interface. The macro itself just expands as a call to qt_metacast that moc generated above. So, Q_DECLARE_INTEFACE defines qobject_cast<Tool *>(object) which is a template specialization that translates to object->qt_metacall("in.forwardbias.tool/1.0");
Q_EXPORT_PLUGIN2
This is the C-function that gets exported in the shared object. It goes something like:
// The pluginName isn't used (see the section static plugins for it's purpose) #define Q_EXPORT_PLUGIN2(pluginName, PluginClass) extern "C" PluginClass *qt_plugin_instance() { return new pluginClass; } extern "C" const char *qt_plugin_verification_data() { return "pattern=QT_PLUGIN_VERIFICATION_DATA\nversion=4.5.3\ndebug=false\nbuildkey=x86_64 linux g++-4 full-config"; }
Note: qt_plugin_instance actually uses a singleton but simplified above for easier understanding
Q_IMPORT_PLUGIN2 defines the C-function that creates an instance of the plugin and also contains an additional C-function that returns the Qt config that the plugin was compiled with.
QPluginLoader
QPluginLoader verifies if a plugin is compatible with the application using the verification data (see example above) that was embedded as part of Q_EXPORT_PLUGIN2. One interesting aspect under unix is that instead of loading the library and resolving the function, Qt will mmap the library and do a string search instead (from the end of the file i.e in reverse). The reason for this seems to be that it is apparently faster that resolving and one can avoid loading incompatible plugins.
Standard Qt plugins
Various parts of Qt can be extended using plugins- codecs, styles, font engines etc. Given a plugin, one will have to cast it to all supported interfaces to determine what the plugin actually implements. To avoid this overhead, Qt defines standard paths into which plugins should be places. For examples, style plugins must be places under plugins/styles/
Static plugins
When Qt is built in static mode, plugins also have to be static. Why? Since Qt is static, it simply cannot load plugins that link to Qt dynamically. The only way is to create static libraries out of all the plugins and link all the static libraries in the target program.
When Qt is built in static mode, the Q_EXPORT_PLUGIN2 macro expands into a C-function qt_plugin_instance_##pluginName(). pluginName helps avoid name conflicts when using mulitple plugins - remember this is statically linked code.
Q_EXPORT_PLUGIN2 generated code to 'register" static plugins. Two more bits need to be done: 1. Someone has to "register" this plugin. When building Qt statically, the developer decides what plugins he wants to ship his app with. Someone needs to register these chosen plugins with Qt' s system. This is achieved using Q_IMPORT_PLUGIN(pluginName). All it does is to create a global static object whose constructor registers the plugin's qt_plugin_instance_##pluginName using qRegisterStaticPluginInstanceFunction. So, now Qt knows that such a plugin exists and knows how to create the plugin but it does not know what it implements! Qt can only run through each and every plugin object and try casting it to each and every standard interface.
2. The plugins itself are static libraries. When our app is linked, we need to link with those static libraries. This is achieved using QTPLUGIN += pluginName in the .pro file (this simply adds -l<plugin>.a to the linker line).
FAQ
1. Can multiple plugins reside in single DLL/.so? You can host multiple plugins of the same type in one DLL but you cannot host different type plugins in a single .so. IOW, you can have two image plugins in a .so but you cannot have a font engine plugin and an image plugin in a single .so (they have to be separate .so/dlls)
2. Can dynamic Qt load static plugins? No.
3. Can static Qt load dynamic plugins? No.