




Before I begin with the article let me first mention that this is my first article on CodeProject. I
have a habit of browsing through almost all of the articles on the net on VC++/MFC. I happened to come
across an article on a Plug-in Architecture
Framework for Beginners. I went through it and found it interesting and worth a try.
So the credit for this article goes to Mr.
Paul Qualls.
Introduction
Ever wondered how your applications can be made flexible/robust without providing the source code to
third parties. Well it's possible. A very common and "the" best example is "WinAmp", supporting
hundreds of plug-ins made by developers around the world. And the number increasing with each minute.
So what's behind this "Plug-In" thing???
To put it simply, it is a feature using which the applications look for various Add-Ins when starting up.
Once all the "Add-Ins" have been located they are loaded by the main app one by one, or selectively so as
to use their in-built features. These "add-ins" are normally DLLs in disguise. They could be residing in a
pre-defined directory or in the search path of Windows.
DLLs are the most effective way to implement plug-in functionality. Even Windows itself uses DLLs
extensively for most of its tasks. DLLs can be delivered easily as a patch or as an upgrade version, without
worrying about re-compiling the main application again and again. Once installed/copied they can be loaded at
runtime by the main application to use the functionality provided using exported functions/classes within the DLL.
Now many of you would question, that how would the application know what all functions are to be called?
The answer to this would be that, all the DLLs must conform to some basic, generic, pre-defined interface.
The call to the function (inside the DLL) can be issued only after knowing the function name. So the main app should,
beforehand, know what all the functions that can be called. So this is the reason why a generic/pre-determined
interface should be developed.
I hope all of you should be familiar with the DLL concept, so I will be starting with the actual
application itself.
Overview of steps involved:
So how should we go about making one??? The basic steps are as follows:
- The main app is initialized (i.e. started)
- The DLLs/Plug-Ins are searched in the directory (may be pre-determined)
- Once "valid" DLLs are found, all or selected DLLs are loaded
- "Init" function is invoked on all loaded DLLs
- The instance and the DLL name/path are cached for future use (method invocation)
- User uses the functionality of the DLLs (using GUI interfaces provided by the DLLs)
- Once done, the main application invokes "Destroy" function of the DLLs one by one to free resources
- The main application exits
Quite simple isn't it??? Well, it is simple given the concept. Actual implementations may be a bit on the
tougher side depending on how big a framework you are developing. My article is just a simple example,
describing the basic functionality used to develop such apps.
A detailed example:
First develop a regular Windows DLL in Visual Studio app-wizard (I used VS 7.0). Export a function
called InitPlugIn
taking a CToolBar
pointer as input and returning a
UINT
, as given below:
extern "C" PLUG_API UINT InitPlugIn (CToolBar *pwndToolBar);
extern "C" PLUG_API void DestroyPlugIn ();
PLUG_API
can be defined as below:
#define PLUG_API __declspec (dllexport)
Define ID_BUTTON
as below:
#define ID_BUTTON ID_BUTTON_PLUGIN_1
#define ID_BITMAP IDB_PLUGIN_1
Once this has been done, create the ID_BUTTON_PLUGIN_1
by going to the resource view, and
then selecting "Resource Symbols" menu option from the edit menu. Now create a 16x15 bitmap image in
the DLL resource. This image is supposed to show up on the toolbar of the main application which we
would be developing shortly. Name that bitmap IDB_PLUGIN_1
.
Now define the "InitPlugIn
" method as under:
PLUG_API UINT InitPlugIn (CToolBar *pwndToolBar)
{
#ifdef DEBUG
TRACE ("Called DLL's InitPlugIn () method\n");
#endif
AFX_MANAGE_STATE (AfxGetStaticModuleState ());
theApp.SetToolBarPointer (pwndToolBar);
theApp.AddButtonToToolBar ();
#ifdef DEBUG
TRACE ("Plug_In_1_Funtion_INIT_PLUG_IN() called...\n");
#endif
return ID_BUTTON;
}
Define DestroyPlugIn
method as below:
PLUG_API void DestroyPlugIn ()
{
#ifdef DEBUG
TRACE ("Called DLL's DestroyPlugIn () method\n");
#endif
AFX_MANAGE_STATE (AfxGetStaticModuleState ());
theApp.RemoveButtonFromToolBar ();
#ifdef DEBUG
TRACE ("Plug_In_1_Funtion_DESTROY_PLUG_IN () called...\n");
#endif
}
The implementation of the SetToolBarPointer()
, AddButtonToToolBar()
,
RemoveButtonFromToolBar()
methods is given in the demo app source code. You all can refer to that.
Basically SetToolBarPointer()
stores the pwndToolBar
pointer of internal use.
AddButtonToToolBar()
uses this pointer to add a button dynamically to the main app's toolbar
when this DLL is initialized. RemoveButtonFromToolBar()
does the opposite, it removes the button
when the DLL is un-loaded or destroyed.
Now this is some of the basic functionality we added to the DLL. There's more in the demo app. You can
refer to it's source code to learn more.
Now our next step would be to make the main application which would in turn use this plug in.
Create a Win32 executable SDI application from the app-wizard. Now our job is to enable the user to view all
available plug-ins in the directory and selectively load them. Since this process is a bit lengthy, but easy, so
you can refer to the source code again for this. As of now, I would be explaining the basic functionality you
have to provide.
According to the steps I described earlier, first we have to find-all available plug-ins. For this we can
use the _findfirst()
and _findnext()
C APIs defined in "io.h
". You could
also use the CFileFind
class instead. The code for this is given in the
PlugInSelectDialog::FindDLLs()
method in the source code. It's job is to find the Plug-In file (*.PLX),
load it using LoadLibrary
method, and then un-load it, verifying in the process whether it is a valid Plug-In or not.
The extension PLX is a custom defined, you can use .DLL instead. Just make sure to search for DLL files
instead of PLX files in the source code. The validation of the DLL can be done in various ways. I used a method
to extract the name of the DLL/plug-in by invoking an exported GetPlugInName
function. If this function exists
and return a name then I consider it as a valid Plug-In. Users can use different methods that suite their needs.
Now before you look at the source code, I must mention that I have used a class named "CDLLManager
" to
handle all the loading/un-loading of DLLs. This is just to make the code much more modular and easy to understand/debug.
This class holds all required information about each DLL loaded such as it's name, path, instance handle, toolbar button ID,
etc. It also takes care of initializing the DLL (remember the InitPlugIn()
method) and also to destroy the
plug-in and free resources used by it (remember the DestroyPlugIn()
method) These functions are invoked by
this class for each and every plug-in loaded. The sequence can be seen in the source code.
Now once the plug-ins are correctly loaded, new buttons would be added to the default toolbar of the main app. The
toolbar pointer of the CMainFrame
class is passed as an argument while calling the InitPlugIn()
method of the plug-in. The function calls to the DLL can be made, by first calling the GetProcAddress()
Win32
method to get the function address within the DLL/plug-in and then using that pointer to invoke that function. One thing to
remember is that we must free each and every DLL loaded once we are through with it, i.e. after using it. Otherwise,
resources may not be freed, and that is not a good thing to do. This can be done using the FreeLibrary()
method.
CDLLManger
class takes care of this using its destructor, where it calls its FreeDLL()
method,
which destroys the DLL and then un-loads it.
Now, with all this done. the user should be able to use the plug-in at runtime by clicking on the toolbar buttons just
added. By default the buttons are disabled, since the compiler cannot find any "MESSAGE MAP" entries for it in the application.
And also we cannot add the message map entry beforehand as the "Main" application doest not know even if the plug-ins existed
or not beforehand, and also what resource IDs they use. After all, the main application has to be designed to be generic and
flexible. So this must be done at runtime (enabling the buttons and handling events on them).
To do this we override OnCmdMsg
in the CMainFrame
class. The method is given below:
BOOL CMainFrame::OnCmdMsg (UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
if (pHandlerInfo == NULL)
{
if (m_bIsPlugInLoaded)
{
for (int nNumDlls = 0; nNumDlls < m_nNumberOfDLLs; nNumDlls++)
{
if (m_pLoadedDLLs[nNumDlls].m_pDLLhInstance != NULL)
{
UINT nTempID = m_pLoadedDLLs [nNumDlls].GetToolBarButtonID ();
if (nTempID == nID)
{
if (nCode == CN_UPDATE_COMMAND_UI)
{
((CCmdUI *) pExtra)->Enable (TRUE);
return TRUE;
}
else
if (nCode == CN_COMMAND)
{
OnCallPlugInManager (nID, nCode, pExtra, pHandlerInfo);
return TRUE;
}
}
}
}
}
}
return CFrameWnd::OnCmdMsg (nID, nCode, pExtra, pHandlerInfo);
}
In this method we catch the CN_UPDATE_COMMAND_UI
event to enable the toolbar button, and we also catch the
CN_COMMAND
event to handle the mouse buttons clicks on the toolbar button. To handle this we call
OnCallPlugInManager()
method of the CMainFrame
class, that in turn calls the PlugInEventHandler
function of the plug-in, passing all the required arguments. This function in the DLL, is supposed to re-direct the call
to CPlug_InApp::OnCmdMsg()
method so that the DLL can handle the message intended for it.
In the demo app provided, the plug-ins don't do very important tasks. They just open the Notepad or Calculator window
depending on what Plug-In was invoked. This is just to explain the concept behind plug-in frameworks.
Problems and Limitations of the example provided:
As I have already stated that this example is not meant to be a full-blown framework for implementing plug-ins. It just
describes the basic steps involved to implement this functionality in your applications. It might seem a bit confusing at
first, but so was I when I first started. Believe me but it's not that tough though. Hopefully shortly I would be giving
a good update for this article, with the code optimized and easier to understand. So pardon me in case of any coding or
documentation problems. You all feel free to contact me for details and problems. :-)
I would not term the following as problems, but would rather refer to them as limitations or workarounds:
The message handling capability of the DLL
As of now I am not able to handle the messages such as clicking on the toolbar button in the DLL directly. The event has
to transferred from CMainFrame::OnCmdMsg()
to the DLL's PlugInEventHandler()
and from there to
the CPlug_InApp::OnCmdMsg()
, so a single event has to travel this much so as to be handled by the DLL. I am not
impressed by this. Any possible "workarounds"? Any help in this regard would be highly appreciated. Actually this is what took
most of my time while developing this example application.
I would really like to handle DLL-specific events within the DLL itself, such as WM_LBUTTONDOWN
,
ON_COMMAND
, WM_MESSAGE
, etc. I tried it before, but the message handlers are never called, may be
due to some silly mistake I had made. But anyways, due to the hurry to submit this code, I used to present method instead.
The DLL till now is not able to access the main applications resources, views, document, etc.
So no concrete work can be done as of now. For example I can draw anything on the view until I make a new function
PlugInDrawOnClient (CPlugSDIView *pView)
. But this involves sharing the header file PlugSDIView.h
to
compile the plug-in, which till now I am not able to figure out what would be the best way to do this. It might come as an update
to this article. Hope I am able to take time out during my job soon, and update this article.
The code is not optimized fully, and may also be a bit confusing to browse through due to the similar variable names
I have used. I would definitely update this too.
While debugging the demo application, I found out that there is some bug in adding/removing toolbar button methods within
the DLL.
I say this due to that fact that upon deleting the toolbar button, the actual image count of the toolbar increases. I am, up to
now, unable to figure out why. You can re-produce this bug by constantly adding and then removing the plug-in a few times and then
checking the image count of the toolbar using the GetImageCount()
method of the CToolBar
class.
Conclusion
So, concluding my first article on CodeProject. I am jumping a bit up and down my seat. At last I've joined the herd after a
long time indeed! But anyways, I know that there would be a thousands silly mistakes and a million errors in the code, but still
for me it's an achievement. I spent a full day writing this article, and two weeks for developing the demo application, taking
my time out from office work. Hope my project manager does not get to know about it.
Any help to overcome the limitations I mentioned would be highly appreciated, especially the event handling limitation!
So till the next update, it's bye from me, and happy coding!!!
Credits
- Mr. Paul Qualls: For the article
"Plug-in Architecture Framework for Beginners"
- Mr. Ingar Pederson for giving me tips on how to go about handling resources in DLLs
- Mr. J�rgen Suetterlin for
providing me help on how to handle messages/events in the DLL, and how to enable the
toolbar buttons without using
OnCmdMsg
in the CMainFrame
class. He has described to me a
method that would make it simple to decide what resource IDs to use in DLLs without the
ID define values clashing with each other.
- Mr. Ehsan
Akhgari for helping me out with the basics of
OnCmdMsg ()
and the CCmdTarget
class.
Because of him I am able to deliver this demo app, working as it is!
He can be reached at http://www.beginthread.com/Ehsan
- 5. Mr. Ashutosh for
providing me help on how to handle messages/events in the DLL. Also helped me in reviewing the code
- Article on "Indicating an empty ListView" on CodeProject
- Article on "Change background color of individual rows in ListView".
Look in the comments for the implementation I used.