Http://www.cppblog.com/yuanyajie/
This article discusses a simple but effective plug-in architecture. It uses C ++, dynamic link library, and is based on the idea of object-oriented programming.
First, let's take a look at what benefits the plug-in mechanism can bring to us, so that we can choose to use it as appropriate.
1. Enhance code transparency and consistency: Because plug-ins usually encapsulate third-party class libraries or code written by others, you need to clearly define interfaces and use clear and consistent interfaces to deal with everything. Your code will not be messy by the conversion program or the special customization requirements of the library.
2. Improve the modularization of the project: Your code is divided into multiple independent modules, which can be placed in the file group in the Child Project. This decoupling makes it easier to reuse the created components.
3. Shorter Compilation Time: if the external library is used internally only to explain the declaration of some classes, the compiler no longer needs to parse the header file of the External library, because the specific implementation is completed in private form.
4. Replacing and adding components: If you need to release patches to users, it is more effective to update separate plug-ins instead of replacing each installed file. When you use a new Renderer or new unit type to scale your game, you can easily implement it by providing a set of plug-ins to the engine.
5. Use the GPL code in the project that closes the source code: Generally, if you use the Code released by GPL, you also need to open your source code. However, If You encapsulate the GPL component in the plug-in, you do not have to publish the plug-in source code.
Introduction
First, let's briefly explain what a plug-in system is and how it works: In a common program, If you need code to execute a special task, you have two options: either you write it yourself, you can either find an existing library that meets your needs. Now your requirements have changed, so you have to rewrite the code or find another different library. Either method causes the code in your framework code to be rewritten that depends on the external library.
Now, we can have another option: In the plug-in system, any component in the project is no longer bound to a specific implementation (for example, the Renderer can be based on OpenGL or direct3d ), they are separated from the Framework Code and put into the dynamic link library through specific methods.
The so-called specific methods include creating interfaces in the Framework Code, which decouples the framework from the dynamic library. The plug-in provides interface implementation. We separate plug-ins from common dynamic link libraries because they are loaded in different ways: programs do not directly link plug-ins, but may be searched in some directories and loaded if they are found. All plug-ins can use a common method to connect to applications.
Common Errors
Some programmers may add a function similar to the following to each dynamic library used as a plug-in when designing the plug-in system: pluginclass * createinstance (const char *);
Then they let the plug-in provide some class implementations. The engine queries the loaded plug-ins one by one with the expected object name until a plug-in returns. This is a typical practice of the "responsibility chain" mode in the design mode. Some smarter programmers will make new designs to enable plug-ins to register themselves in the engine, or use custom implementations to replace the internal default implementations of the engine:
Void dllstartplugin (pluginmanager & PM );
Void dllstopplugin (pluginmanager & PM );
The main problem of the first design is that the objects created by the plug-in factory must be converted using reinterpret_cast <>. Generally, a plug-in is derived from a common base class (pluginclass), which references some insecure feelings. In fact, this is meaningless. The plug-in should "Silently" respond to the request from the input device and then submit the result to the output device.
In this structure, the work required to provide multiple different implementations of the same interface becomes abnormal and complex. If the plug-in can register itself with different names (such as direct3drenderer and openglrenderer ), however, the engine does not know which specific implementation is effective for users. If all possible implementations are hard-coded into the program, the purpose of using the plug-in structure is meaningless.
If the plug-in system is implemented through a framework or library (such as a game engine), the architect will certainly expose the function to the application. In this way, there will be some problems, such as how to use plug-ins in applications, how plug-ins author engine header files, etc. This includes the possibility of version conflicts between the three.
Independent Factory
Interfaces are clearly defined by the engine, rather than plug-ins. The engine defines the interface to guide the plug-in on what to do, and the plug-in provides specific functions. Let the plug-in register the special implementation of its own engine interface. Of course, it is stupid to directly create plug-in implementation class instances and register them. In this way, all possible implementations exist at the same time, occupying both memory and CPU resources. The solution is the factory class, which only aims to create another class instance at the request. If the engine defines an interface to communicate with the plug-in, it should also define an interface for the factory class:
Template <typename interface>
Class factory {
Virtual Interface * Create () = 0;
};
Class Renderer {
Virtual void beginscene () = 0;
Virtual void endscene () = 0;
};
Typedef factory <Renderer> rendererfactory;
Select 1: plug-in Manager
Next, we should consider how the plug-ins register their factories in the engine, and how the engine actually uses these registered plug-ins. One option is to work well with the existing code, which is done by writing the plug-in manager. This allows us to control which components are allowed to be extended.
Class pluginmanager {
Void registerrenderer (STD: auto_ptr <rendererfactory> Rf );
Void registerscenemanager (STD: auto_ptr <scenemanagerfactory> SMF );
};
When an engine needs a Renderer, It accesses the plugin manager to see which Renderer has been registered through the plugin. Then the plug-in manager is required to create the desired Renderer. The plug-in Manager uses the factory class to generate the Renderer, And the plug-in manager does not even need to know the implementation details.
The plug-in is composed of dynamic libraries. The latter exports a function that can be called by the plug-in Manager to register itself:
Void registerplugin (pluginmanager & PM );
The plug-in Manager simply loads all DLL files in a specific directory and checks whether they have an export function named registerplugin. Of course, you can also use XML documents to specify which plug-ins will be loaded.
Option 2: fully integrated
In addition to the plug-in manager, you can also design a code framework from the ground up to support plug-ins. The best way is to divide the engine into several subsystems and build a system core to manage these subsystems. It may be like the following:
Class kernel {
Storageserver & getstorageserver () const;
Graphicsserver & getgraphicsserver () const;
};
Class storageserver {
// Provided for the plug-in to register a new reader
Void addarchivereader (STD: auto_ptr <archivereader> Al );
// Query all registered readers until the specified format can be opened.
STD: auto_ptr <archive> openarchive (const STD: string & sfilename );
};
Class graphicsserver {
// Used by the plug-in to add a driver
Void addgraphicsdriver (STD: auto_ptr <graphicsdriver> AF );
// Obtain the number of valid image drivers
Size_t getdrivercount () const;
// Return driver
Graphicsdriver & getdriver (size_t index );
};
There are two subsystems that use "server" as the suffix. The first server maintains a list of valid image loaders. Each time you want to load an image, the image loaders are queried one by one, until a specific implementation is found to be able to process images in a specific format. Another subsystem has a list of graphicsdrivers, which are used as the renderers factory. You can use direct3dgraphicsdriver or openglgraphicsdrivers to create direct3drenderer and openglrenderer respectively. The engine provides a valid driver list for users to choose from. by installing a new plug-in, the new driver can also be added.
Version
In the above two optional methods, you are not required to place specific implementations in the plug-in. Assume that your engine provides a default Implementation of the reader to support custom file package formats. You can put it into the engine itself and register it automatically when storageserver starts.
Another question is not discussed: if you are not careful, plug-ins that do not match the engine (for example, outdated ones) will be loaded. Some changes to the sub-system class or plug-in Manager are enough to cause a change in the memory layout. When mismatched plug-ins attempt to register, conflicts or even crashes. What's annoying is that these are difficult to find during debugging. Fortunately, it is very easy to identify outdated or incorrect plug-ins. The most reliable method is to place a preprocessing constant in your core system. Any plug-in has a function that can return this constant to the engine:
// Somewhere in your core system
# Define myengineversion 1;
// The plugin
Extern int getexpectedengineversion (){
Return myengineversion;
}
After this constant is compiled into the plug-in, when the constant in the engine changes, the getexpectedengineversion () method of any plug-in that is not re-compiled will return the previous value. Based on this value, the engine rejects plug-ins that do not match. To make the plug-in work again, you must recompile it. Of course, the biggest danger is that you forget to update the constant value. In any case, you should have an automatic version management tool to help you.
Http://www.nuclex.org/articles/building-a-better-plugin-architecture
Download sample code.