[Legacy version]
C++
Articles
- Algorithms
- C++ 11
- Graphics and multimedia
- How-To
- Language Features
- Unix/Linux programming
- Source Code
- Standard Library
- Tips and Tricks
- Tools and Libraries
- Visual C++
- Windows API
- Articles
- Making a Plugin System
Making a Plugin System
Score: 4.1/5 (483 votes) Now, lets say that you are making a program, maybe a game, and you decide that you want to make it moddable without your intervention. Now, of course, you think of how that might be possible, and without forcing the users to inject code directly into your executable, or modifying the source code directly. How would you do this?Well, of course, the answer is a plugin system. I'll briefly explain how it works: A plugin system is simply where a specified folder is searched for DLLs (or the like), and if any are found, adds the contents to the program. Of course, because the program doesn't actually know what is in the DLLs, the normal way is for the DLL's to define a set entry point and calling functions defined by the program itself, which can then use the functionality exposed in those DLLs. The way this can be done is up to you, whether defining functions to implement or maybe getting the DLL to provide an instance of a base class, and then use the functionality from that. In this article, I am briefly going to demonstrate both of those options. First, though, lets look at how to actually load libraries at all.
Loading a library
Well, lets start with the basics. To load a DLL at runtime, simply call
LoadLibrary
, with the parameter being the file path of the DLL to load. However, when you think about this, this isn't much help, is it? We want to load a variable number of DLLs, whose names cannot be known at compile time. So, this means that we need to find all the DLLs that are plugins, and then load them.Now, the easiest way of doing this is using WinAPI's
FindFile
functions, using a file mask to collect all the .dll files. Though, this can have the problem that you suddenly try loading the DLLs that your program needs to run! This is the reason why programs normally have a 'plugins' folder: If you tried to load all the DLLs just from the directory of your program, you might start trying to load non-plugin DLLs. The seperation into a specified plugin folder helps prevent this from happening.Now, enough talking, here is some example code of how to loop through all the files in a directory and load the values for each:
|
|
Well, that is fairly complicated. Just thought I'd mention now, you do need a C++11 compiler to be able to compile these examples, otherwise some of the things like raw string literals will not compile. Also, if you use a Unicode compiler, you will need to specify that it is using wide strings.
Now, we have loaded all our plugins, but if we don't free them when we are done, we will cause a memory leak, and that can become a real problem in bigger projects. However, because we have stored all our handles in a vector, freeing them isn't actually that hard:
|
|
Actually doing something with our library
OK, no we can load the libraries. The thing is, it doesn't actually do anything yet. Lets change that. For starters, we should define a header file for the DLLs to include: This defines the functions and classes that we want them to export. I've decided to show two things here: How to export a polymorphic class and how to export a function. Once you get the idea, though, most things are fairly easy. Anyway, lets define our header file:
|
|
Now, for the complicated bit. We need to load these functions from the DLL's that we loaded earlier. The function to do this is called
GetProcAddress()
, which returns a pointer to the function in the DLL with the name you specify. Because it doesn't know what type of function it is getting, however, we need to explicitly cast that pointer returned to a function pointer of the appropriate type. Add this code to the earlier example:
|
|
And that is it for loading and using a plugin! You probably want to store the objects / names in their own lists, but it doesn't really matter, this is just an example.
Building the plugins
Now, there is one thing left: Actually building the plugins. This is very simple, comparitively. You need to
#include "main.hpp"
so as to get the classes, and then simply implement the functions. The main()
function is the only thing you have to watch out for: For one, it isn't actually called main anymore! Here is just a basic main function (you don't normally need more than this):
|
|
Getting the source code
Here I have posted a link to the source code for your convenience. This was compiled using the MinGW-w64 toolchain, but it should work for most Windows compilers. The examples require C++11 support (mostly for raw string literals and std::unique_ptr), and are developed with ANSI encoding in mind (rather than Unicode). This shouldn't make much of a difference, just go through and change the string literals to be long string literals, and use wide strings instead (std::wstring).By way of project files, unfortunately I can't provide them all, there is merely a GCC makefile. To find out how to compile the project using your compiler, look at the documentation for that compiler. Probably the bit of information that you are least likely to know is how to generate a DLL, and how to define symbols when compiling.
Download the source code
Final Remarks
If you don't understand anything in this article, first check out MSDN (the MicroSoft Developer Network). In fact, I would have to quote MSDN as my main source of information for this article. Here are some links to relevant pages that you may be interested in looking at:DLL loading functions:
- LoadLibrary, load a DLL for use.
- FreeLibrary, free a DLL from the address space.
- GetProcAddress, get the address of a function exported by a DLL.
- DllMain, the 'main' function for a DLL.
File related functins:
- WIN32_FIND_DATA structure, the structure returned by the file finding functions.
- FindFirstFile, finds the first file of a type.
- FindNextFile, continues a search from FindFirstFile.
- FindClose, close the file finding handle.
Please let me know if there is anything unclear in this article, an error that you find, or you have any suggestions for updates to this article!
Update - Using the plugin system across different compilers
Due to this issue that has been encountered, I have decided to update this article slightly. The above has been kept the same, however. Basically, I will put how to fix issues such as using DLLs built by other compilers.The Problem
If you use different compilers, different versions of the compiler, or even different settings on the same compiler, the DLL will be generated differently and may cause crashes with the application it is linked to. This is because C++ is not binary standardized - i.e. There is no requirement for the same source code on different compilers to behave in the same way. This is especially true of things like the C++ standard library, were different compilers can have different implementations, which can cause problems with the program.Another thing that can change between compilers (and as a general rule, WILL change) is the name mangling of functions. In the example above, the function
getObj
was replaced with the following name: _Z6getObjv
. However, this particular name is dependent on the compiler that produces it: This name was from a MinGW compiler, an MSVS compiler will produce something different, and an Intel compiler something else again. This can also cause problems.Some Solutions
For the above problems, there are a few solutions. The first (non-solution) is just to always use the same compiler. This is useful for if you or your company are the only people providing plugins for this application, so you can ensure that you are using the same compiler settings as when you exported your primary application.Another solution is to avoid using the standard library. The standard library is very useful, but due to different implementations it can cause problems with the usage of the objects: My compiler's std::string and another compilers std::string might look and behave the same, but in reality can be very different on the inside, so using one instead of the other can cause problems. Possible workarounds are to pass the raw data associated with the object, rather than the object itself.
For example, you can still use std::string and std::vector<int> in your programs, but for the exported interface you would pass a
const char*
or an int*
instead, and just convert to and from in the process.Which brings me to the final problem: The name mangling. C++ compilers will often mangle the names of functions and variables differently if the compiler has different options set (such as level of optimization or debug/release builds). However, C compilers do no name mangling, which means that the functions name will not change based on the compiler options. Here is how to say that we are exporting the functions with 'C' linkage:
|
|
Then, when you implement the functions, you need to specify that you are using C linkage for them as well:
|
|
This is to tell the compiler to use C linkage, which normally guarantees that all compilers will see the functions in the same way, and also has the bonus side effect of getting rid of a lot of the strange symbols that may otherwise appear. Of course, this means that you can only export C-style functions and structures, but that is the price to pay for the compatibility that you receive.
Attachments: [plugin-src.zip]
Home page | Privacy policy© cplusplus.com, 2000-2022 - All rights reserved - v3.3.3
Spotted an error? contact us 标签:std,function,functions,load,System,c++,DLL,Making,compiler From: https://www.cnblogs.com/chenyalin/p/17118347.html