A simple plug-in architecture pattern for C++ applications on Win32
By George Mihaescu
Summary: This article presents a simple and elegant solution to creating components that can be dynamically deployed and loaded by a C++ Win32 application without the need of any framework / infrastructure or technology (such as COM). It relies on basic C++ mechanisms and two commonly used Win32 API calls.
Download the code here (VC++ projects + source).
Note: code snippets in the document and code available for download are now based on VS .Net 2005 / CRT 8.0
The Problem
You have a C++ Win32 application to which you want to be able to “attach” components dynamically, developed either by yourself or by other parties. In the context of this document I will call those components plug-ins, by analogy with the well-known web browser add-on components. In the same manner with the browsers, you want your application to be aware of such plug-ins that were developed and deployed possibly long after the application itself was developed and deployed. Ideally, you don’t want the application to even need to restarted – what you’d like is to drop the plug-in at a know location (e.g. somewhere under the application installation directory) even as the application is running, and the application all of a sudden has enhanced functionality. Can this be done in a simple and reliable way?
The Solution
Many readers will immediately dismiss the problem and this article by saying “of course, that’s what COM is all about”. But I’m not in favor of using COM unless there is a very compelling reason for it. Generally, if it can be done without COM (without making the code overly complex – I don’t want to re-implement COM), then why bother with COM? Say COM and you say code complexity, registration issues (such as registration requiring certain user security privileges), dependency on registry, application usually needing to be re-started, etc. I argue below that I can meet the requirements of this problem without COM, in a much simpler and reliable way.
High-level design
My solution is based on marrying the C++ polymorphic mechanism with the Win32 APIsLoadLibrary and GetProcAddress.
The principle is that the main application publishes a contract with the plug-ins in the form of an interface that the plug-ins are expected to implement in order to meet the contract. Then the main application will scan a known location on disk (e.g. a “plugins” directory relative to its installation directory) and attempt to load all plug-ins (using LoadLibrary API) that export implementations of this interface (determined by using the GetProcAddressAPI). Each plug-in is packaged in its own DLL (or multiple DLLs) that must be deployed at the location the application expects them in ordered to be detected and loaded by the application.
Low-level design
As C++ does not offer a language construct to model the concept of an interface, we will use the next best thing available: an abstract base class.
Also, because the Win32 API GetProcAddress uses a function name as a parameter, while our plug-ins are C++ (because they need to provide a concrete derivative of the abstract base class) and because C++ does function name mangling, our plug-ins will need to export as a minimum one C-style function to act as the class factory. To keep things balanced and because the application has (in theory) no way of knowing what allocation strategy each plug-in factory function uses, it only makes sense to ask plug-ins to also export the counter-part of the factory function, another C-style function to act as the plug-in clean-up / tear-down procedure.
So, to sum it up:
· The main program has an abstract class through which it will use all dynamically loaded plug-ins. It also has a few lines of code to scan a known location and look for DLLs that export two known C-style functions: the plug-in factory and the plug-in clean-up.
· Each plug-in has a class implementing the abstract class in the main program, and is packaged as a Win32 DLL exporting two C-style functions: the plug-in factory and the plug-in clean-up.
This is illustrated in the diagram below. As you can see, there is no registration required, no need for the user to have special privileges on the machine, no framework / runtime dependency other than what you already have: C++ and Win32.
The code
Below is a sample “contract” (abstract class) in the main program that plug-ins will need to implement. Of course, the methods of this class are going to be specific to what your plug-ins need to do:
//
// Abstract base class ("interface") for the concrete plugin implementations
class IPlugin
{
public:
//Add whatever functions each plugin needs to implement
//Those below are just dummy examples to illustrate the principle
//returns the name of the concrete plugin
virtual const char* Get_Name () const = 0;
//does the actual data processing
virtual void Process_Data () = 0;
};
/
//Extern "C" functions that each plugin must implement in order to be
//recognized as a plugin by us.
// Plugin factory function
//extern "C" IPlugin* Create_Plugin ();
// Plugin cleanup function
//extern "C" void Release_Plugin (IPlugin* p_plugin);
Below is the code in the main program that scans for plug-ins, determines that they are indeed exporting the two C-style functions it expects from a plug-in, then loads and executes each plug-in found:
#include "IPlugin.h" //for the IPlugin abstract base
//convenience typedef for the pointers to the 2 functions we
//expect to find in the plugins
typedef IPlugin* (*PLUGIN_FACTORY)();
typedef void (*PLUGIN_CLEANUP)(IPlugin*);
int main(int argc, char* argv[])
{
//get the program's directory
char dir [MAX_PATH];
::GetModuleFileName (NULL, dir, MAX_PATH);
//eliminate the file name (to get just the directory)
char* p = ::strrchr (dir, '\\');
*(p + 1) = 0;
//find all DLLs in the plugins subdirectory
char search_parms [MAX_PATH];
::strcpy_s (search_parms, MAX_PATH, dir);
::strcat_s (search_parms, MAX_PATH, "plugins\\*.dll");
WIN32_FIND_DATA find_data;
HANDLE h_find = ::FindFirstFile (search_parms, &find_data);
BOOL f_ok = TRUE;
while (h_find != INVALID_HANDLE_VALUE && f_ok)
{
//load each DLL and determine whether it is exporting
//the functions we care about
char plugin_full_name [MAX_PATH];
::strcpy_s (plugin_full_name, MAX_PATH, dir);
::strcat_s (plugin_full_name, MAX_PATH, "plugins\\");
::strcat_s (plugin_full_name, MAX_PATH, find_data.cFileName);
HMODULE h_mod = ::LoadLibrary (plugin_full_name);
if (h_mod != NULL)
{
PLUGIN_FACTORY p_factory_function =
(PLUGIN_FACTORY) ::GetProcAddress (h_mod, "Create_Plugin");
PLUGIN_CLEANUP p_cleanup_function =
(PLUGIN_CLEANUP) ::GetProcAddress (h_mod, "Release_Plugin");
if (p_factory_function != NULL &&
p_cleanup_function != NULL)
{
//yes, this DLL exposes the 2 functions we need,
//it is a plugin we can use!
//invoke the factory to create the plugin object
IPlugin* p_plugin = (*p_factory_function) ();
//show which plugin it is, and let the plugin
//do the processing
printf ("Now working with plugin: %s\n",
p_plugin ->Get_Name ());
p_plugin ->Process_Data ();
//done, cleanup the plugin by invoking its
//cleanup function
(*p_cleanup_function) (p_plugin);
}
::FreeLibrary (h_mod);
}
//go for the next DLL
f_ok = ::FindNextFile (h_find, &find_data);
}
return 0;
}
And finally, here is the code for one such plug-in:
#include "stdio.h"
#include "..//MainProgram//IPlugin.h"
// A concrete plugin implementation
// Plugin class
class Plugin1 : public IPlugin
{
public:
//returns the name of the concrete plugin
const char* Get_Name () const
{
return "Plugin1";
}
//does the actual data processing
virtual void Process_Data ()
{
for (int i = 0; i < 3; i++)
{
printf ("Plugin 1 is processing....\n");
}
printf ("Plugin 1 processing done!\n");
}
};
extern "C"
{
// Plugin factory function
__declspec(dllexport) IPlugin* Create_Plugin ()
{
//allocate a new object and return it
return new Plugin1 ();
}
// Plugin cleanup function
__declspec(dllexport) void Release_Plugin (IPlugin* p_plugin)
{
//we allocated in the factory with new, delete the passed object
delete p_plugin;
}
}
But wait: what about the promise that the user won’t even have to re-start the application after deploying a new plug-in? For that, just throw in a couple more Win32 APIs: as the application starts, create a low-priority thread that calls FindFirstChangeNotification /FindNextChangeNotification / WaitForSingleObject or WaitForMultipleObjects and this thread can notify every time a valid plug-in DLL is deployed at the known location – so that the application can act (start using the plug-in / ask user whether to enable the plug-in, etc.).
Other enhancements
- As mentioned above, in most cases if you want the application to dynamically sense when plug-ins are deployed and run them without having to re-start, you will need a disk monitoring thread like the one described above.
- You will probably want to implement versioning on your program’s contract with the plug-ins. After all, it’s very likely that your plug-ins interface will evolve over time, and you want the program to be able to ignore / reject plug-ins that were not written for its version of the contract (e.g. user deploys plug-ins written for a more recent version of the program on an older version of the program and vice versa). Such a versioning protocol can be implemented in the abstract class that represents your program’s contract with the plug-ins, so that the program can decline using plug-ins that don’t conform to its versioning requirements.
- Sometimes the plug-ins may need to add to the application’s help (CHM) files. I will not get into the details, but it can be done quite easily if the main program’s help file is properly written for file merging. If the plug-in is deployed together with its help file, the Windows help engine can automatically merge the main program’s CHM help file with each of the plug-ins CHM help files, resulting in a seamless user experience. Maybe I will address this in another article – until then, Google “Merging Help Files at Run Time”. I have done this and I know it works fine without any pain.
I have implemented this pattern since 1998 with great results. One of the free programs available from this site (daVinci) uses this architecture to implement parsers for different file formats. As a user needs a parses for another file format, we implement it and make the parser available for download on our site. The user downloads the parser and deploys it under the “parsers” subdirectory of the application (without even closing the application), and all of a sudden the application can handle the new file format.
备注:
最近一个系统里面就采用这种方法,程序主体部分负责数据的产生,处理由插件来执行,插件可以有很多,定义好接口后由很多人同时开发。