The program in this tutorial is split into 3 parts; the first part is as follows:
- Design an abstract base class which will be used to create client plugins.
- Add OS-specific bindings to the API in order to improve cross-platform functionality.
- Create a private library-loader class which will handle all resource loading/unloading using RAII.
Part 2 dives into the runtime environment:
- Start
main()with the input parametersargcandargv. - For each input string in
argc: - Verify the parameter is a valid path to a shared libary,
- Attempt to load a shared library using the input path name.
- Run the plugin.
For part 3:
- Create a plugin class which inherits from a pre-defined API base type.
- Create a factory method to instantiate the derived class from within the shared library.
- Create a deletion method which will destroy any instantiated class methods using the shared library.
- Compile the library as either a DLL or SO file.
- Run the file using the program created in part 1.
PART 1:
Since this is a simple test, we can really just design our plugin base class to be as simple as possible. The idea here will be that a plugin will display a simple message to the screen:
class plugin {
public:
virtual ~plugin() = 0
virtual void printMessage() const = 0;
};
inline plugin::~plugin() { // An inline destructor will avoid requiring a .cpp file in each // user-created library.}
This seems plenty basic enough so place it into a single header. This header will be used by both the main program (defined in part 2) and the plugin library (created in part 3). Now in order to load this from an external library at runtime, we'll need to supply the compiler a bit of information about the current operating system. For example, the compiler needs to know whether it will be importing or exporting symbols from a library into the final program executable. It also needs to know how functions will be called from the external library. This is important because if a shared library contains a different way to call functions that the program which loads the library, either the library won't load or the program will crash. With regard to OS-specific settings, Linux and OSX will used shared libraries using a ".so" (shared object) extension while Windows uses ".dll" (dynamic link-library). There are several other major differences but discussing that is beyond the scope of this tutorial. Here's the setup that will be used to determine how to load
plugin objects from within our executable.
/*
* Windows-specific settings for a dynamic library
*/
#ifdef _WIN32
/* Standard dynamic library extension */
#ifndef PLUGIN_EXT
#define PLUGIN_EXT ".dll"
#endif
/* Calling convention for functions loaded at runtime. */
#ifndef PLUGIN_CALL
#define PLUGIN_CALL __stdcall
#endif
/* Determine if the plugin will be imported at runtime or exported to a library. */
#ifdef BUILD_PLUGIN
#define PLUGIN_API __declspec(dllexport)
#else
#define PLUGIN_API __declspec(dllimport)
#endif
/*
* Dynamic library settings for Linux and OSX
*/
#else
/* Standard dynamic library extension */
#ifndef PLUGIN_EXT
#define PLUGIN_EXT ".so"
#endif
/* Calling convention for functions loaded at runtime. */
#ifndef PLUGIN_CALL
#define PLUGIN_CALL __cdecl
#endif
/* Determine if the plugin will be imported at runtime or exported to a library. */
#ifndef PLUGIN_API
#define PLUGIN_API
#endif
#endif
This can either be placed into the same header file as the
plugin class, or into a global header file and included before the plugin class. The next thing that needs to be done for our plugin object is to define a way to instantiate it from within a shared library, then delete it. I uses two C-style functions in order to take care of this since our library loader (shown in part 2) can only call C functions. The reason is because C++ files introduce name mangling, something which helps the compiler differentiate between class objects, function overloads, and namespace scoping issues. I also included typedefs for the factory and deleter functions in order to simplify their calls in other parts of the code:
extern "C" plugin* PLUGIN_API PLUGIN_CALL createPluginInstance();
typedef plugin* (*pluginFactory_t)();
extern "C" void PLUGIN_API PLUGIN_CALL destroyPluginInstance(plugin* const pPlugin);
typedef void (*pluginDeleter_t)(plugin* const);
For the final part of the plugin's header file, make sure that you have a string associated with both of the above functions. The string will be used later on to tell out library loader what function should be loaded from the DLL/SO.
#ifndef PLUGIN_FACTORY_NAME
#define PLUGIN_FACTORY_NAME "createPluginInstance"
#endif
#ifndef PLUGIN_DELETER_NAME
#define PLUGIN_DELETER_NAME "destroyPluginInstance"
#endif
And that's it for the plugin object's header, and part 1! Part 2 will cover the ability to load plugin objects from libraries and run them. Then to wrap up, part 3 is going to show how to make a plugin of your own and run it!
Here's the complete header:
This article was written by a real thinking writer. I agree many of the with the solid points made by the writer. I’ll be back.
ReplyDeletecplugin