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 code written by third-party class libraries or other people, 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 engineering Modularization
: Your code is clearly divided into multiple independent modules, which can be placed in a file group in the sub-project. This decoupling makes it easier to reuse the created components.
3. Shorter Compilation Time
: If you only want to explain the declaration of some classes, and these classes use external libraries internally, the compiler no longer needs to parse the header files of the External library, because the specific implementation is completed in private form.
4. Replace and add Components
: If you need to release patches to users, it is more effective to update individual 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 published 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
A simple explanation of 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
To meet 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.
Institute
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 of their loading methods.
Different: the program does not directly link the plug-in, but may be searched in some directories and loaded if 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
One major problem of design is that the objects created by the plug-in factory must be converted using reinterpret_cast <>. Normally, the plug-in starts from the common base class (
PluginClass) is derived, it will reference some unsafe 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
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), but the engine does not know which specific implementation is effective for the user's choice. If the list of all possible implementations is hardcoded into the program, the purpose of using the plug-in structure is also
It makes no sense.
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
Connect
Ports 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, directly create
Plug-in implementation class instances and registration is stupid. 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 at the request
Create an instance of another class. 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 );
};
This
There are two subsystems, they 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
To process images in a specific format. Another subsystem has a list of GraphicsDrivers, which are used as the Renderers factory. Yes
Direct3DgraphicsDriver or OpenGLGraphicsDrivers, which are responsible for Direct3Drenderer and
OpenGLRenderer creation. 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.
Now
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 memory Layout
Changed. A conflict or even crash may occur when mismatched plug-ins attempt to register. 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;
}
In
After this constant is compiled into the plug-in, when the constant in the engine changes, the getExpectedEngineVersion of any plug-in that has not been re-compiled
() The method returns 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 constants.
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.