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).
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 APIs LoadLibrary 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 GetProcAddress
API). 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.