Korelib design. This document describes the goals and design of Korelib. The document is structured into several sections, each section providing describing various aspects of Korelib. The order of sections is rather important, since one section may use concepts defined/described in the previous section(s). Goals The primary goal of Korelib is to provide an easy and consistent way of creating modular (plugin-based) applications. A typical Korelib application consists of a main app binary (executable) and a set of plugins (dynamic libraries) which are loaded at run-time. Using Korelib gives your application both a great deal of flexibility - you can develop a plugin or replace a plugin with another one whithout needing to recompile your main application - and scalability - by using load-as-needed of plugins your application can scale up and down depending on the features you need at run-time. Also, as a side effect of the way Korelib is implemented (see "Implementation Overview" section), Korelib can be used as lightweight component model for your application. All you have to do is: inherit from various Korelib classes (such as kore::Module or kore::ServiceProviders), fill in the necessary stuff (such as kore::Module::Info and kore::ServiceProvider::services() ), and register your components to the various Korelib managers. Last, but not the last, since Korelib is cross-platform - the current version is working on Linux, FreeBSD, BeOS, AtheOS and Win32 - it hides the platform-dependant details of loading dynamic libraries, providing a consistent cross-platform API, and thus helping you in creating cross-platform applications. Implementation Overview There are a lot of applications using the "plugin" (application module loaded at run-time, most likely from a dynamic library) concept. Most of the implementations, while being very simple and straightforward to use, have a few limitations, such as: - too application-specific; each application has it's own vision over what a plugin is. That means you have to learn a different API and programming model for each application you are writing plugin(s) for. More, you cannot write generic ("generic" as in "which can be used by more apps") plugins or use a plugin written for one app with another app. Most of the time you have to redesign/rewrite the plugin, or at least provide an "API bridge" between the 2 plugin systems. - no differentiation between the dynamic library (which is just a "plugin container", that is, just a file holding all the data and code for the plugin), and the actual plugin (which you use in your app). Since the two are different concepts (thus each of them having different attributes and actions that can be performed - different API's, in one word), you end up by either having both *full* API's put together in one API (which means bloat) or having the two API's trimmed down and put together (less bloat, but also less flexibility). - the plugin model is too simple and inflexible, requring non-standard hacks (which may break in future versions of the app) and even changes to the plugin API (which may break the other exisiting plugins). Also, by not making a clear distinction between the dynamic library and the plugin, it may be not possible to bundle two or more related plugins into one single library. - no introspective information; sometimes it's usefull to be able to find some "introspective" information about a given plugin, such as plugin name, plugin version, API version needed by the plugin (the application may want to refuse loading a plugin which requires an older API version, binary incompatible with the current API version), and so on. - no central management of resources, such as: plugins, services and capabilities; Korelib solves the above limitations (as well as a few others) by adding several levels of indirection to the way plugins are handled. At a first look, loading and using plugins with Korelib may seem a little bit complex, but things are really clear and easy once you understand the Korelib programming model. Also, the initial code you need for creating a Korelib plugin can be more complex than the code needed by other plugin-based apps; that's because Korelib makes a clear distinction between the plugin container and the plugin itself, and you have to write at least two classes: one for the container and one for the plugin. However, you may use one of the samples that come with Korelib as a template for your plugins, and only edit the relevant parts. Concepts and Terminology Because of the apparent complexity of Korelib, there is an obvious need of defining the concepts Korelib is based on, as well as defining a consistent terminology to be used with Korelib. Sometimes the terms used to describe a concept have different meaning with Korelib than with other plugin-based systems. That's mostly because Korelib is more fine-grained (thus giving more flexibility) than the other plugin systems, and it is using two or more concepts for in places where the other systems are using only one single concept. Ie. kore::Plugin is actually only the plugin container (dynamic library), while the kore::Module (and it's subclass kore::ServiceProvider) is the "actual plugin". There are three main orthogonal concepts in Korelib: Module, Service and Plugin. 1. The Module (sometimes called "component") is Korelib's basic structural and functional unit. Basically everything in Korelib (except for some helper classes) is a module. Also, if you plan to use Korelib in your application, you will most likely make your classes inherit (directly or indirectly) the kore::Module class. What differentiates a module from a regular C++ class is that each module has a Info property associated to it. The module Info is used for holding a few module meta-properties (information), such as: module name, type and description, module and Korelib API version, and so on. Your application may query your module for these meta-properties and make some decisions depending on that (ie. it may refuse to use a module who's version is different from the version your app is expecting). Korelib also provides a centralised way of managing modules via it's kore::ModuleManager (please note that the ModuleManager is a Module itself, actually a special type of Module - ServiceProvider). Your application has to register all it's Modules to the ModuleManager right after the Modules are created and unregister them from the ModuleManager before destroing them. The ModuleManager can be queried at any time for the list of the Modules that are registered to it. 2. Since one size doesn't fit all, it's not possible to provide one generic API for all Korelib's modules. That is, different modules may have different capabilities (ie. perform different tasks), and therefore we need a way to tell what capabilities a particular module has. More, the application should be able to both query a module for it's capabilities at run-time and find out which module(s) have a certain capability. Korelib handles the task by splitting each module capability into two parts: a dynamic (run-time) part and a static (compile-time) part. The dynamic part of a module capability is called Service and it provides all the necessary information (such as service name and description) for querying a module for its capabilities at run-time. The static part of a module capability is a class interface (API) that the module has to implement. The class interface is usually an abstract C++ class (having all it's methods pure virtual); the module has to inherit from it and implement all of its methods. "Module X has the Y capability" means both "Module X provides the Y service" and "Module X implements the Y interface/API". A Korelib ServiceProvider is a Module (that is, kore::ServiceProvider inherits from kore::Module) which provides at least one Service. The main difference between a ServiceProvider and a "regular" Module is that the ServiceProvider can be queried for the Services it provides. Same as for modules, Korelib has a ServiceManager to handle the Services and ServiceProviders in a centralised manner. Each newly created ServiceProvider has to be registered to the ServiceManager (actually the services it provides is what get's registered) right after it was created, and unregistered from the ServiceManager before getting destroyed. The ServiceManager can be queried at any time for: what services are registered, what providers are registered, which services are provided by a given ServiceProvider, which ServiceProvider(s) are providing a given service, and so on. Please note that a ServiceProvider may provide more than one Service, and a service may be provided by more than one ServiceProvider. 3. The Korelib Plugin is a cross-platform representation of a dynamic linked library (dll). The Plugin has a dual role: 1) Provide a cross-platform abstraction for dll's and hide platform-dependant implementation details; 2) Provide hooks (API) for various dynamic loading related tasks. Ie. the Plugin::pluginLoaded() and Plugin::unloadingPlugin() hooks are triggered after loading (pluginLoaded()) the dll and before unloading (unloadinPlugin()) the dll. The user may override these methods in order to perform some initialization/cleanup. Please note that a kore::Plugin is a kore::Module, but not a kore::ServiceProvider, which means a Plugin itself cannot provide any Services. The right way to make a dll provide one or more Korelib services is to bundle up one or more ServiceProviders into a dll and make the Plugin take care of registering/unregistering those ServiceProviders to the ServiceManager. Plugins can be loaded/unloaded via the kore::PluginLoader and they have to be registered/unregitered to the kore::PluginManager. Structure Overview Korelib consists of several classes that are implementing the concepts described in the "Concepts and Terminology" section. In order to avoid possible nameclashes with other class libraries, all the Korelib classes are using the namespace "kore". There are two kinds of classes: modules (those inheriting from kore::Module) and helper classes. The helper classes are used by the modules in order to provide various module meta-information. Instaces of helper classes are non-mutable (read-only). Here is the list of helper classes for the current version of Korelib: kore::Version kore::Module::Info kore::ServiceProvider::Service kore::Version is used for holding common version information, such as major/minor/revision number and version string (in ".." format). Also, it defines a set of operators for testing source/binary compatibility of two versions, and for version matching/comparison. kore::Version instances are returned by the kore::Module::Info::version() and kore::Module::Info::APIVersion() methods. kore::Module::Info holds kore::Module meta-information, such as module name, type, description and version, as well as Korelib API version needed by the module. kore::Module::Info instances are returned by kore::Module::info() method. kore::ServiceProvider::Service is Korelib's implementation of the Service concept. Each kore::ServiceProvider::Service has a service name, provider and service description. kore::ServiceProvider::Service instances are returned by the kore::ServiceProvider::services(), kore::ServiceProvider::service(...), kore::ServiceManager::registeredService() and kore::ServiceManager::registeredServices() methods. The kore::Module is the base class for all the module classes. Here is the class hierarchy for the current version of Korelib: kore::Module kore::Plugin kore::ServiceProvider kore::Kernel kore::ModuleManager kore::PluginLoader kore::PluginManager kore::ServiceManager kore::Module is Korelib's implementation for the Module concept. Each kore::Module instance has a kore::Module::Info associated to it. Also, the kore::Module class provides four hooks/callbacks that are triggered before/after the module get's registered/unregistered to a ModuleManager. The classes inheriting from kore::Module may override these methods in order to perform some useful tasks. kore::Module instances are returned by the kore::Module::Info::module() and kore::ModuleManager::registeredModules() methods. kore::Plugin is Korelib's implementation of the Plugin (dynamic library) concept. the kore::Plugin class provides four hooks/callbacks triggered when the Plugin get's loaded/unloaded/activated/deactivated. kore::Plugin instance are returned by the kore::PluginLoader::openPlugin() and kore::PluginLoader::runPlugin() methods. kore::ServiceProvider is a kore::Module which provides at least one kore::ServiceProvider::Service. The kore::ServiceProvider API extends the kore::Module API by addind three methods for quering the provider for the kore::Services it provides: kore::ServiceProvider::services() and kore::ServiceProvider::service(...). Also, the kore::ServiceProvider has eight hooks/callbacks triggered before/after registering/unregistering the provider/services to a kore::ServiceManager. kore::ServiceProvider instance are returned by the kore::ServiceProvider::Service::provider(), kore::ServiceManager::registeredProvider(...) and kore::ServiceManager::registeredProviders() methods. kore::Kernel is the provider of the "Kore/Kernel/Kernel" service. This class is a singleton and it can be used as a starting point for accessing all the other modules in an application. The kore::Kernel::instance() static method returns the single kore::Kernel instance (it also creates the instace first time when it's invoked). The kore::Kernel::serviceManager() method returns the kore::ServiceManager associated to this Kernel. The kore::Kernel instance is returned by kore::Kernel::instance() and dynamic_cast(kore::ServiceManager::registeredProvider("Kore/Kernel/Kernel")) calls. kore::ModuleManager is the provider for the "Kore/Kernel/Module Manager" service. The kore::ModuleManager class has four methods for registering/unregistering one or more kore::Modules, and one method for getting the list of registered kore::Modules. It also takes care of triggering the right registering/unregistering callbacks in kore::Module. kore::ModuleManager instances are returned by the dynamic_cast(kore::ServiceManager::registeredProvider("Kore/Kernel/Module Manager")) call. kore::PluginLoader is the provider for the "Kore/Kernel/Plugin Loader" service. The kore::PluginLoader is a cross-platform loader for dynamic linked libraries. It hides the platform-specific implementation details and provides a consistent API for loading/initialising/unloading kore::Plugins, error handling and conversion between plugin name and dll name. It also takes care of triggering the right callbacks in kore::Plugin. kore::PluginLoader instances are returned by the dynamic_cast(kore::ServiceManager::registeredProvider("Kore/Kernel/Plugin Loader")) call. kore::PluginManager is not implemented yet. kore::ServiceManager is the provider for the "Kore/Kernel/Service Manager" service. The kore::ServiceManager class has eight methods for registering/unregistering one or more providers/services to it, six methods for quering for registered services and five methods for quering for registered providers. It also takes care of triggering the right registering/unregistering callbacks in kore::ServiceProvider. kore::ServiceManager instances are returned by the kore::Kernel::serviceManager() and dynamic_cast(kore::ServiceManager::registeredProvider("Kore/Kernel/Service Manager")) calls. Implementation Detail Programming Model This section describes how to use Korelib to create your own Plugins, as well as how to load a Plugin and use the services it provides: 1. Decide what services you want to provide. Create a kore::ServiceProvider::Service instance for each service and fill in the necessary information (service name and a short description of the service). 2. Write the interface classes for each service. Each interface class should be a C++ abstract class defined in it's own header file. Decide what API (methods) will each interface provide and make all the methods pure virtual. 3. Write the providers for the services you want to provide. Please note that one provider can provide more than one service and one service can be provided by more than one provider. It's up to you to decide how many providers you will have and which/how many services will each one provide. The only restrictions when writing a provider are: the provider has to inherit (directly or indirectly) from kore::ServiceProvider and the provider has to implement (ie. inherit from) all the interfaces of the services it provides. Fill in the necessary information (such as kore::Module::Info and services) for each provider. Hint: for the module Info you may either override the kore::Module::info() method and return you own Info instance or use the kore::Module::setInfo(...) protected method to set your provider's Info (in which case you don't have to override the info() method). Same, for the list of services you may either override the kore::ServiceProvider::services() method and return the list of services you provide, or use the kore::ServiceProvider::addService() to add services to your provider (in which case you don't need to override the services() method). You may also want to check Korelib's providers (such as Kernel, ServiceManager, and so on) for examples of how to write a provider. Also, don't forget to set the provider field of each kore::Service so that it points to the right provider. Obviously, if you have two providers which are providing the same service, you'll need two kore::Service instance, one for each provider. The two instance will have the same service name and different provider and service description. 4. If you want your providers in a separate dll loaded at run-time, you'll have to write a kore::Plugin class and fill in the kore::Module::Info for it. Your plugin will have to: - "export" itself, so that the kore::PluginLoader can get a reference to it. This is done by using: extern "C" { PLUGIN_MAIN_HDR(libHandle,libName,libPath,libFlags); } in its header file, and: PLUGIN_MAIN_BODY(,libHandle,libName,libPath,libFlags); in its cpp file. The needs to be replaced with the actual name of your plugin class. Also, your plugin constructor needs to have the following signature: MyPlugin::MyPlugin(HMODULE libhandle, const char* libname, const char* libpath, int flags):Plugin(libhandle,libname,libpath,flags); - take care of registering/unregistering your providers to the kore::ServiceManager each time the plugin get's activated/deactivated. Each time your plugin get's activated, the kore::Plugin::initPlugin() callback will get triggered, so this is the right place to put your registration code. A typical initPlugin() should look like this: void MyPlugin::initPlugin() { // get a reference to the kore::ServiceManager ServiceManager* sm = Kernel::instance()->serviceManager(); // get a reference to the kore::ModuleManager ModuleManager* mm = dynamic_cast(sm->registeredProvider("Kore/Kernel/Module Manager")); // create your providers prv1 = new MyFirstProvider(); prv2 = new MySecondProvider(); // register them to the kore::ServiceManager sm->registerProvider(prv1); sm->registerProvider(prv2); // since each kore::ServiceProvider is also a kore::Module, try to // register your providers to the kore::ModuleManager if( mm ) { mm->registerModule(prv1); mm->registerModule(prv2); } } Each time your module get's deactivated the kore::Plugin::finalizePlugin() callback get's triggered, so this is the right place to put your unregistration code. A typical finalizePlugin() should look like this: void MyPlugin::finalizePlugin() { // get a reference to the kore::ServiceManager ServiceManager* sm = Kernel::instance()->serviceManager(); // get a reference to the kore::ModuleManager ModuleManager* mm = dynamic_cast(sm->registeredProvider("Kore/Kernel/Module Manager")); // unregister them from the kore::ServiceManager sm->unregisterProvider(prv1); sm->unregisterProvider(prv2); // since each kore::ServiceProvider is also a kore::Module, try to // unregister your providers from the kore::ModuleManager if( mm ) { mm->unregisterModule(prv1); mm->unregisterModule(prv2); } // destroy your providers delete prv1; prv1 = 0; delete prv2; prv2 = 0; } Important: the current Korelib implementation does NOT guarantee that kore::Plugin::finalizePlugin() get's triggered before kore::Plugin::unloadingPlugin(), so it is possible that the Plugin will get unloaded before being finalize, and that may be a cause for various run-time errors (ie. if you use one of the providers after the plugin was unloaded, your application will crash). In order to fix that, you need to override kore::Plugin::unloadingPlugin() and add a call to finalizePlugin() in there. 5.In order to use the newly created Plugin in your application you need to load and initialize the plugin (using kore::PluginLoader::runPlugin(...) ), query the kore::ServiceManager for the various services provided by the providers bundled in your plugin, cast the kore::ServiceProvider(s) returned by the previous query to the interface associated to the queried service, and use the inteface's API to get access to the various methods. Here is a short sample: // called when the application is created/initialised void appInit() { // get a reference to the kore::ServiceManager ServiceManager* sm = Kernel::instance()->serviceManager(); // get a reference to the kore::ModuleManager ModuleManager* mm = dynamic_cast(sm->registeredProvider("Kore/Kernel/Module Manager")); // get a reference to the kore::PluginLoader PluginLoader* loader = dynamic_cast (sm->registeredProvider("Kore/Kernel/Plugin Loader")); if( loader ) { // try to load and initialise your plugin. We assume the plugin is contained // in a dll named "libmy_plugin.so" (or "my_plugin.dll" on Win32). Plugin* myplugin = loader->runPlugin("my_plugin"); // check if the plugin dll was found if( myplugin ) { // since each kore::Plugin is also a kore::Module, // try to register our plugin to the kore::ModuleManager if( mm ) mm->registerModule(myplugin); // note: future Korelib versions may require that you register // your plugin to the kore::PluginManager, as well. } else // the plugin dll was not found. Display a diagnostic message printMessage( loader->lastError() ); } } // a function that performs some useful task void doSomeWork() { // get a reference to the kore::ServiceManager ServiceManager* sm = Kernel::instance()->serviceManager(); // query the kore::ServiceManager for one of the service provided by your plugin // we assume that the service name is "My Plugin/My Services/First Service" ServiceProvider* prv = sm->registeredProvider("My Plugin/My Services/First Service"); // cast the above kore::ServiceProvider to MyFirstInterface // we assume that every provider of the "My Plugin/My Services/First Service" // is implementing MyFirstInterface MyFirstInterface* sp1 = reinterpret_cast(prv); // do some work ... // use the provider. We assume that MyFirstInterface has method named met1() sp1->met1(); // do some more work ... } // called when the application prepares to exit void appCleanup() { // get a reference to the kore::ServiceManager ServiceManager* sm = Kernel::instance()->serviceManager(); // get a reference to the kore::ModuleManager ModuleManager* mm = dynamic_cast(sm->registeredProvider("Kore/Kernel/Module Manager")); // get a reference to the kore::PluginLoader PluginLoader* loader = dynamic_cast (sm->registeredProvider("Kore/Kernel/Plugin Loader")); // get a reference to the your previously loaded plugin. This can be done by // either using a global variable pointing to your plugin, or by querying // the kore::ModuleManager: Plugin* myplugin = 0; if( mm ) { Module** modules = mm->registeredModules(); // modules is a null-terminated array of modules registered to the manager for(int i=0; modules[i]; i++) // search the list of registered modules by module name if( !strcmp( modules[i]->info()->name(), "my_plugin" ) { // found the plugin. cast the kore::Module to kore::Plugin and // exit the loop. myplugin = dynamic_cast(modules[i]); break; } // if the plugin was found if( myplugin ) // unregister it from the kore::ModuleManager mm->unregisterModule( myplugin ); } // if the plugin was found if( myplugin ) { // unload it if( loader ) loader->closePlugin(); } // perform the final cleanup by deleting the kore::Kernel delete Kernel::instance(); } // sample main() function void main(void) { appInit(); doSomeWork(); appCleanup(); } Thanks and Credits to: - an Infinite Number of Monkeys for typing in Korelib - my employer - theKompany.com (http://www.thekompany.com) - for providing the necessary resources (such as salaries, and an Inifinite Number of Typewriters) for the Infinite Number of Monkeys - SteQven M. Christey for designing the wonderful IMPS (Infinite Monkey Protocol Suite) as described in RFC 2795 (http://www.faqs.org/rfcs/rfc2795.html) Version and Copyright Version 0.1 Copyright(C) 2000 Catalin Climov