Hi all, Some thoughts on the plugin infrastructure. Feel free to bash them :)
# PluginManager I've been looking at the licq_*plugin.h files, and it seems a bit awkward that a plugin should have to be loaded before you can get the version, description etc. I propose moving that information to to separate text files (see attached icq.protocol and encryption.plugin for examples) and then creating CPluginManager to retrieve that information and handle loading and unloading of plugins. The plugin manager would also be responsible for loading dependencies of plugins requested to be loaded. A suggestion for how the interface could look is in plugin_manager.h. If you think this seems like a good idea, I'm willing to implement it. # Plugins... With the addition of CPluginManager, the base plugin classes will be simplified. I've created an example of CGeneralPlugin (see licq_generalplugin.h), but CProtocolPlugin should be similar. This also adds a macro to make it easier for plugin developers. An example plugin is in myplugin.h. My opinion is that plugin developers shouldn't have to know that the plugin is loaded with dlopen, nor that the plugin is run in a separate thread. I mean, they should be aware of it, but they shouldn't have to care. Thread programming isn't easy, so it should be hidden as much as possible. We don't want to intimidate new plugin developers. # ... and threads This brings me to my last topic: How will the callbacks be called? If they are called from the daemon's thread, access to data in the plugin has to be protected with locks which forces the plugin developer to care about threads => bad (see above). An alternative is to implement an event loop in CPlugin (which CGeneralPlugin and CProtocolPlugin inherits from, perhaps private). Since the callbacks should be called synchronous (or?), it could be implemented something like this: The daemon calls the plugin's addToEventQueue function, passing along the callback and CEventData pointer. The function locks a list and appends the data to it. After unlocking the list the daemon goes to sleep. The plugin's event loop checks this list, finds the callback and executes it. After storing the result, it wakes up the daemon thread. The daemon reads the result and continues with the next callback. This way, plugin developers don't have to care about threads since all gory details are hidden in CPlugin => good. Of course, there are some problems (or issues): - If the plugin gets stuck, the daemon also hangs (a timeout on the sleep should solve this: pthread_cond_timedwait). - how to integrate this event loop with qt's or gtk's? - sending signals plugin => daemon. Should the daemon poll plugins? - other stuff... If I've overlooked something basic, please tell me. My thread programming experience is limited ;) // Erik -- Thinking of Emacs as just an editor is like thinking of the space shuttle as just another part of the public transportation system. -- Dave Kenny, comp.emacs Erik Johansson http://ejohansson.se
#ifndef LICQ_GENERALPLUGIN_H #define LICQ_GENERALPLUGIN_H class CLicqDaemon; #define LICQ_GENERAL_PLUGIN(className) \ extern "C" { \ CGeneralPlugin *createGeneralPlugin(CLicqDaemon &daemon) \ { return new className (daemon); } \ void destroyGeneralPlugin(CGeneralPlugin *instance) \ { delete instance; } \ void *startGeneralThread(void *_plugin) { \ CGeneralPlugin *plugin = static_cast<CGeneralPlugin *>(_plugin); \ plugin->m_exitStatus = plugin->run(); \ return 0; \ } \ } class CGeneralPlugin { public: CGeneralPlugin(CLicqDaemon& daemon) : m_exitStatus(-1), m_daemon(daemon) {} virtual ~CGeneralPlugin(); std::string getBuildDate() const; std::string getBuildTime() const; /// Returning from this will stop the plugin. virtual int run() = 0; /// Automatically set to return value from run(). int m_exitStatus; protected: CLicqDaemon& m_daemon; }; #endif // LICQ_GENERALPLUGIN_H // kate: space-indent on; indent-width 2; indent-mode cstyle; // kate: remove-trailing-space on; replace-trailing-space-save on;
[Licq Plugin] Name = Encryption Version = 2.0 Library = licq_encryption_plugin.so Author = Mr Anonymous Dependencies = ICQ | MSN, GPG Short Description = Encrypt your ICQ or MSN communication Long Description = This plugin can be used with ICQ and/or MSN. You also need GPG. Remember, NSA is _always_ watching you...
#ifndef PLUGIN_MANAGER_H #define PLUGIN_MANAGER_H #include <boost/noncopyable.hpp> #include <vector> #include <string> typedef std::vector<std::string> TStringList; /** * Keeps a register over available (general) plugins and protocols. * Implemented as a singleton. An alternative would be to make * CLicqDaemon a friend to allow it to call the constructor. * * CLicqDaemon should implement a method getPluginManager() to enable * plugins to retrive information about other plugins. */ class CPluginManager : public boost::noncopyable { public: ~CPluginManager(); static CPluginManager& instance(); /// Reloads all *.plugin and *.protocol files. bool reloadRegister(); /// Returns the names of all available plugins. const TStringList& getAvailablePluginsList(); const TStringList& getAvailableProtocolsList(); /// Returns e.g. the version or the author for the given plugin. std::string getPluginInformation(const std::string& pluginName, const std::string& property); std::string getProtocolInformation(const std::string& protocolName, const std::string& property); /// Returns the names of all loaded plugins. const TStringList& getLoadedPluginsNames(); const TStringList& getLoadedProtocolsNames(); /// Returns the number of instances loaded for the given plugin. int getLoadedPluginInstances(const std::string& pluginName); int getLoadedProtocolInstances(const std::string& protocolName); /// Loads the given plugin and returns instance number (or 0 if the load failed). /// This implies loading the library unless a instance of the plugin already exists, /// creating the plugin class instance and running it in a new thread. int loadPlugin(const std::string& pluginName); int loadProtocol(const std::string& protocolName); /// Unloads the given instance (or all) of the given plugin. /// This implies stoping the thread, deleting the plugin class instance and unloading the library /// if no other instances from the plugin is running. bool unloadPlugin(const std::string& pluginName, int instance = 0); // 0 == all instances bool unloadProtocol(const std::string& protocolName, int instance = 0); // 0 == all instances private: CPluginManager(); class CPluginManagerPrivate *d; }; #endif // PLUGIN_MANAGER_H // kate: space-indent on; indent-width 2; indent-mode cstyle; // kate: remove-trailing-space on; replace-trailing-space-save on;
#include "licq_generalplugin.h" LICQ_GENERAL_PLUGIN(MyPlugin); class MyPlugin : public CGeneralPlugin { public: MyPlugin(CLicqDaemon& daemon) : CGeneralPlugin(daemon) { // Load settings... // Setup callbacks... // Do some other fancy stuff... } ~MyPlugin(); int run() { // How should this be implemented? while (1) ; return 0; } };
[Licq Protocol] Name = ICQ Version = 0.1 Library = licq_icq.so Multiple Instances = true Author = Homer Simpson <[EMAIL PROTECTED]>, Friendly Guy Short Description = Connect to the ICQ network Long Description = A long description talking about how superior ICQ is to MSN...