Making a Plugin System

Making a Plugin System

Score: 3.7/5 (44 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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// A list to store our DLL handles
std::vector<HINSTANCE> modules;
// The data for each file we find.
WIN32_FIND_DATA fileData;

// Find the first DLL file in out plugins folder,
// and store the data in out fileData structure.
HANDLE fileHandle = FindFirstFile(R"(.\plugins\*.dll)", &fileData);

if (fileHandle == (void*)ERROR_INVALID_HANDLE ||
    fileHandle == (void*)ERROR_FILE_NOT_FOUND) {
    // We couldn't find any plugins, lets just
    // return for now (imagine this is in main)
    return 0;
}

// Loop over every plugin in the folder, and store
// the handle in our modules list
do {
    // Load the plugin. We need to condense the plugin
    // name and the path together to correctly load the
    // file (There are other ways, I won't get into it here)
    HINSTANCE temp = LoadLibrary((R"(.\plugins\)" + 
                         std::string(fileData.cFileName)) .c_str());

    if (!temp) {
        // Couldn't load the library, continue on
        cerr << "Couldn't load library " << fileData.cFileName << "!\n";
        continue;
    }

    // Add the loaded module to our list of modules
    modules.push_back(temp);
// Continue while there are more files to find
} while (FindNextFile(fileHandle, &fileData));


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:
1
2
for (HINSTANCE hInst : modules)
    FreeLibrary(hInst);




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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#ifndef __MAIN_HPP_INCLUDED__
#define __MAIN_HPP_INCLUDED__

// includes we need
#include <string>
#include <memory>

// Test to see if we are building a DLL.
// If we are, specify that we are exporting
// to the DLL, otherwise don't worry (we
// will manually import the functions).
#ifdef BUILD_DLL
    #define DLLAPI __declspec(dllexport)
#else
    #define DLLAPI
#endif // BUILD_DLL

// This is the base class for the class
// retrieved from the DLL. This is used simply
// so that I can show how various types should
// be retrieved from a DLL. This class is to
// show how derived classes can be taken from
// a DLL.
class Base {
public:
    // Make sure we call the derived classes destructors
    virtual ~Base() = default;

    // Pure virtual print function, effect specific to DLL
    virtual void print(void) = 0;

    // Pure virtual function to calculate something, 
    // according to an unknown set of rules.
    virtual double calc(double val) = 0;
};


// Get an instance of the derived class
// contained in the DLL.
DLLAPI std::unique_ptr<Base> getObj(void);

// Get the name of the plugin. This can
// be used in various associated messages.
DLLAPI std::string getName(void);

#endif // __MAIN_HPP_INCLUDED__ 


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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
do {
    ...

    modules.push_back(temp);
    
    // Typedefs for the functions. Don't worry about the
    // __cdecl, that is for the name mangling (look up
    // that if you are interested).
    typedef std::unique_ptr<Base> (__cdecl *ObjProc)(void);
    typedef std::string (__cdecl *NameProc)(void);

    // Load the functions. This may or may not work, based on
    // your compiler. If your compiler created a '.def' file
    // with your DLL, copy the function names from that to
    // these functions. Look up 'name mangling' if you want
    // to know why this happens.
    ObjProc objFunc = (ObjProc)GetProcAddress(temp, "_Z6getObjv");
    NameProc nameFunc = (NameProc)GetProcAddress(temp, "_Z7getNamev");

    // use them!
    std::cout << "Plugin " << nameFunc() << " loaded!\n";
    std::unique_ptr<Base> obj = objFunc();
    obj->print();
    std::cout << "\t" << obj->calc() << std::endl;
} while (...);


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):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
extern "C" DLLAPI BOOL APIENTRY DllMain(HINSTANCE hInst, 
                                        DWORD reason, 
                                        LPVOID reserved)
{
    switch (reason) {
        case DLL_PROCESS_ATTACH:
            // attach to process, return FALSE to fail
        break;

        case DLL_PROCESS_DETACH:
            // detaching from process
        break;

        case DLL_THREAD_ATTACH:
            // attach to thread within process
        break;

        case DLL_THREAD_DETACH:
            // detach from thread within process
        break;
    }

    // return success
    return TRUE;
}




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:
File related functins:
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  notbinary 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:
1
2
3
4
5
6
7
8
9
10
#ifdef __cplusplus  // if we are compiling C++
extern "C" {        // export the functions with C linkage
#endif

// ... your DLL exported functions, e.g.
const char* getName(void);

#ifdef __cplusplus
}
#endif 

Then, when you implement the functions, you need to specify that you are using C linkage for them as well:
1
2
3
4
5
6
7
8
9
10
11
// ...

const std::string pluginName = "TestPlugin";

extern "C"
const char* getName(void) {
    // just extrapolating on information given above: we can still
    // use the C++ Standard Library within functions, just you can't
    // pass them as the return value or function arguments
    return pluginName.c_str();
}


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.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2015年来,sublime text3更新相当的频繁,功能上也有较大的改进和更新。 现在已是3078版本,具体更新内容(从我上个上传版本3072之后)如下(摘自官方网站): Build 3078 Release Date: 19 March 2015 Fixed a plugin_host regression in 3077 Build 3077 Release Date: 19 March 2015 Fixed a regression in 3075 that caused the default preferences to be marked as unsaved Fixed a performance regression with large folders introduced in 3067 Partially reworked plugin_host communication OSX: Fixed a regression in 3076 that caused excess CPU usage Build 3075 Release Date: 10 March 2015 See also the Forum Thread Build Systems: Build systems can now be explicitly selected again Build Systems: Renamed "keyfile" to "keyfiles", now accepting a list of files that can trigger the build system (e.g., ["Makefile", "makefile"]) Improved change detection for files that disappear and reappear, as happens with disconnected network drives Windows: Added workaround for broken std::condition_variable in MSVC 2012, fixing a crash in plugin_host Updated to a never version of leveldb, fixing constant low level CPU usage if the index becomes corrupted Fixed a crash that could occur in directory scanning when directories are being rapidly deleted and recreated Transient sheets (e.g., as created by Goto Anything when previewing files) are no longer added to the Recently Closed list Windows: Added more descriptive errors when the Update Installer fails to rename a folder Build 3074 Release Date: 3 March 2015 See also the Forum Thread Build Systems: Variants can now be selected directly when pressing Primary+B (See this discussion for more information) Posix: Fixed new files not respecting the umask permission flags OSX: Workaround for an OS issue with zero size windows and OpenGL views Windows: Fixed incorrect window sizing after making a maximised window full screen Windows: Fixed access denied errors that could occur when saving with atomic_save disabled remember_open_files setting is now hidden, and defaults to false. Note that this change will have no effect if the hot_exit setting is left at its default value of true Build 3073 Release Date: 28 February 2015 See also the Forum Thread Fixed a Goto Definition regression in 3072 Build System choices are remembered, so the user will be prompted to choose a build system less often Added Edit Project to the Command palette
Mastering Vim: Build a software development environment with Vim and Neovim By 作者: Ruslan Osipov ISBN-10 书号: 1789341094 ISBN-13 书号: 9781789341096 出版日期: 2018-11-30 pages 页数: (330) Vim is a ubiquitous text editor that can be used for all programming languages. It has an extensive plugin system and integrates with many tools. Vim offers an extensible and customizable development environment for programmers, making it one of the most popular text editors in the world. Mastering Vim begins with explaining how the Vim editor will help you build applications efficiently. With the fundamentals of Vim, you will be taken through the Vim philosophy. As you make your way through the chapters, you will learn about advanced movement, text operations, and how Vim can be used as a Python (or any other language for that matter) IDE. The book will then cover essential tasks, such as refactoring, debugging, building, testing, and working with a version control system, as well as plugin configuration and management. In the concluding chapters, you will be introduced to additional mindset guidelines, learn to personalize your Vim experience, and go above and beyond with Vimscript. By the end of this book, you will be sufficiently confident to make Vim (or its fork, Neovim) your first choice when writing applications in Python and other programming languages. Contents 1: GETTING STARTED 2: ADVANCED EDITING AND NAVIGATION 3: FOLLOW THE LEADER – PLUGIN MANAGEMENT 4: UNDERSTANDING THE TEXT 5: BUILD, TEST, AND EXECUTE 6: REFACTORING CODE WITH REGEX AND MACROS 7: MAKING VIM YOUR OWN 8: TRANSCENDING THE MUNDANE WITH VIMSCRIPT 9: NEOVIM 10: WHERE TO GO FROM HERE What You Will Learn Get the most recent Vim, GVim, and Neovim versions installed Become efficient at navigating and editing text Uncover niche Vim plugins and pick the best ones Discover multiple ways of organizing plugins Explore and tailor Vim UI to fit your needs Organize and maintain Vim configuration across environments Write
Module 1: Bootstrap 4 By Example Chapter 1: Getting Started 3 Getting Bootstrap 4 Setting up the framework 5 Folder structure 6 Warming up the sample example 6 Bootstrap required tags 8 Building our first Bootstrap example 10 The container tag 11 Optionally using the CDN setup 14 Community activity 15 Tools 16 Bootstrap and web applications 16 Browser compatibility 17 Summary 18 Chapter 2: Creating a Solid Scaffolding 19 Understanding the grid system 19 Building our scaffolding 20 Setting things up 21 Offset columns 23 Completing the grid rows 24 Nesting rows 24 Finishing the grid 26 Fluid container 28 We need some style! 28 There are headings everywhere 30 Playing with buttons 31 [ ii ] More typography and code tags 32 Manipulating tables 37 Styling the buttons 40 Like a boss! 41 Final thoughts 42 Box-sizing 42 Quick floats 43 Clearfix 43 Font definitions for typography 44 Summary 44 Chapter 3: Yes, You Should Go Mobile First 47 Making it greater 47 Bootstrap and the mobile-first design 49 How to debug different viewports at the browser 49 Cleaning up the mess 52 Creating the landing page for different devices 53 Mobile and extra small devices 54 Tablets and small devices 59 Desktop and large devices 61 Summary 62 Chapter 4: Applying the Bootstrap Style 65 Changing our grid layout 65 Starting over the grid system 67 The header 67 The introduction header 68 The about section 71 The features section 73 The price table section 75 The footer 78 Forming the forms 81 Newsletter form 81 Contact form 83 The sign-in form 87 Images 89 Helpers 91 Floating and centering blocks 91 Context colors 91 Spacing 92 Responsive embeds 93 Summary 94 [ iii ] Chapter 5: Making It Fancy 95 Using Bootstrap icons 95 Paying attention to your navigation 99 Until the navigation collapse 100 Using different attachments 102 Coloring the bar 103 Dropping it down 103 Customizing buttons dropdown 106 Making an input grouping 107 Getting ready for flexbox! 109 Understanding flexbox 109 Playing with Bootstrap and flexbox 111 Summary 112 Chapter 6: Can You Build a Web App? 113 Understanding web applications 113 Creating the code structure 114 Adding the navigation 114 Adding the search input 117 Time for the menu options! 118 The option at the thumbnail 118 Adding the Tweet button 119 Customizing the navigation bar 120 Setting up the custom theme 120 Fixing the list navigation bar pseudo-classes 121 You deserve a badge! 122 Fixing some issues with the navigation bar 123 Do a grid again 127 Playing the cards 127 Learning cards in Bootstrap 4 128 Creating your own cards 129 Adding Cards to our web application 130 Another card using thumbnails 133 Implementing the main content 135 Making your feed 136 Doing some pagination 142 Creating breadcrumbs 143 Finishing with the right-hand-side content 144 Summary 149 [ iv ] Chapter 7: Of Course, You Can Build a Web App! 151 Alerts in our web app 152 Dismissing alerts 153 Customizing alerts 153 Waiting for the progress bar 155 Progress bar options 156 Animating the progress bar 157 Creating a settings page 158 Pills of stack 159 Tabs in the middle 163 Adding the tab content 165 Using the Bootstrap tabs plugin 165 Creating content in the user info tab 166 The stats column 169 Labels and badges 171 Summary 173 Chapter 8: Working with JavaScript 175 Understanding JavaScript plugins 175 The library dependencies 176 Data attributes 176 Bootstrap JavaScript events 177 Awesome Bootstrap modals 177 Modal general and content 179 The modal header 179 The modal body 180 The modal footer 180 Creating our custom modal 181 A tool for your tip 183 Pop it all over 186 Popover events 189 Making the menu affix 191 Finishing the web app 193 Summary 198 Chapter 9: Entering in the Advanced Mode 199 The master plan 200 The last navigation bar with flexbox 201 The navigation search 205 The menu needs navigation 206 Checking the profile 211 [ v ] Filling the main fluid content 212 From the side stacked menu 213 I heard that the left menu is great! 214 Learning the collapse plugin 216 Using some advanced CSS 220 Filling the main content 221 Rounding the charts 223 Creating a quick statistical card 226 Getting a spider chart 229 Overhead loading 232 Fixing the toggle button for mobile 234 Summary 235 Chapter 10: Bringing Components to Life 237 Creating the main cards 237 The other card using Bootstrap components 241 Creating our last plot 244 Fixing the mobile viewport 246 Fixing the navigation menu 250 The notification list needs some style 253 Adding the missing left menu 254 Aligning the round charts 256 Learning more advanced plugins 258 Using the Bootstrap carousel 258 Customizing carousel items 260 Creating slide indicators 260 Adding navigation controls 262 Other methods and options for the carousel 263 The Bootstrap spy 264 Summary 269 Chapter 11: Making It Your Taste 271 Customizing a Bootstrap component 271 The taste of your button 272 Using button toggle 273 The checkbox toggle buttons 274 The button as a radio button 275 Doing the JavaScript customization 276 Working with plugin customization 276 The additional Bootstrap plugins 282 Creating our Bootstrap plugin 283 Creating the plugin scaffold 284 [ vi ] Defining the plugin methods 289 The initialize method and plugin verifications 289 Adding the Bootstrap template 290 Creating the original template 292 The slide deck 293 The carousel indicators 294 The carousel controls 295 Initializing the original plugin 295 Making the plugin alive 296 Creating additional plugin methods 298 Summary

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值