Click here to Skip to main content
Click here to Skip to main content
Go to top

Plug-In framework using DLLs

, 25 Jun 2002
Rate this:
Please Sign up or sign in to vote.
Explains how to develop applications that support plug-ins
<!-- Download Links --> <!-- Article image -->

Sample Image - PlugIn1.jpg

Sample Image - PlugIn2.jpg

Sample Image - PlugIn3.jpg

Sample Image - PlugIn4.jpg

Sample Image - PlugIn5.jpg

<!-- Add the rest of your HTML here -->

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:

  1. The main app is initialized (i.e. started)
  2. The DLLs/Plug-Ins are searched in the directory (may be pre-determined)
  3. Once "valid" DLLs are found, all or selected DLLs are loaded
  4. "Init" function is invoked on all loaded DLLs
  5. The instance and the DLL name/path are cached for future use (method invocation)
  6. User uses the functionality of the DLLs (using GUI interfaces provided by the DLLs)
  7. Once done, the main application invokes "Destroy" function of the DLLs one by one to free resources
  8. 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:

// Exported Plug-In methods
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:

// Used to initialize the Plug-In and various resources
PLUG_API UINT InitPlugIn (CToolBar *pwndToolBar)
{
#ifdef DEBUG
        TRACE ("Called DLL's InitPlugIn () method\n");
#endif
 
        AFX_MANAGE_STATE (AfxGetStaticModuleState ());
 
        // Store main app's toolbar pointer in a local member variable
        theApp.SetToolBarPointer (pwndToolBar);
        // Add a toolbar button to the main app's toolbar
        theApp.AddButtonToToolBar ();
 
#ifdef DEBUG
        TRACE ("Plug_In_1_Funtion_INIT_PLUG_IN() called...\n");
#endif
 
        return ID_BUTTON;
}

Define DestroyPlugIn method as below:

// Used to destroy the Plug-In and free resources help by it
PLUG_API void DestroyPlugIn ()
{
#ifdef DEBUG
        TRACE ("Called DLL's DestroyPlugIn () method\n");
#endif
 
        AFX_MANAGE_STATE (AfxGetStaticModuleState ());
 
        // Remove the previously added button from the toolbar
        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 is NULL, then handle the message
   if (pHandlerInfo == NULL)
   {
      // If Plug-Ins have been loaded
      if (m_bIsPlugInLoaded)
      {
         // Iterate through all loaded Plug-Ins
         for (int nNumDlls = 0; nNumDlls < m_nNumberOfDLLs; nNumDlls++)
         {
            // If Plug-In instance is valid and not NULL
            if (m_pLoadedDLLs[nNumDlls].m_pDLLhInstance != NULL)
            {
               // Get toolbar button ID from CDLLManager
               UINT nTempID = m_pLoadedDLLs [nNumDlls].GetToolBarButtonID ();
 
               // If toolbar button ID is equal to nID on OnCmdMsg ()
               if (nTempID == nID)
               {
                  // If command is and CN_UPDATE_COMMAND_UI
                  if (nCode == CN_UPDATE_COMMAND_UI)
                 {
                    // Update UI element state. Enable the button
                    ((CCmdUI *) pExtra)->Enable (TRUE);
 
                    return TRUE;
                 }
                 else
                 // If it is CN_COMMAND. Generated by clicking the button
                 if (nCode == CN_COMMAND)
                 {
                    // Handle CN_COMMAND Message
                    // Call OnCallPlugInManager () to redirect the call
                    // to the Plug-Ins Event Handler method
                    OnCallPlugInManager (nID, nCode, pExtra, pHandlerInfo);
 
                    return TRUE;
                 }
               }
            }
         }
      }
   }
 
   // Else let windows handle the COMMAND
   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. Smile | :)

I would not term the following as problems, but would rather refer to them as limitations or workarounds:

  1. 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.

  2. 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.

  3. 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.

  4. 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

  1. Mr. Paul Qualls: For the article "Plug-in Architecture Framework for Beginners"
  2. Mr. Ingar Pederson for giving me tips on how to go about handling resources in DLLs
  3. 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.
  4. 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. 5. Mr. Ashutosh for providing me help on how to handle messages/events in the DLL. Also helped me in reviewing the code
  6. Article on "Indicating an empty ListView" on CodeProject
  7. Article on "Change background color of individual rows in ListView". Look in the comments for the implementation I used.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Mohit Khanna
Web Developer
India India
Living in New Delhi (India). Did my Engg (Comp. Science) in 2000, and since then working on and off with VC++. It's my passion, and would really love to learn it to the very detail. Presently working as a software engg in a Telecom based firm. I've got around 3yrs experience in NMS (network management systems). Well thats all about me for now. Feel free to contact me...

Comments and Discussions

 
Generalto mimic the winamp - lib vs. dll PinmemberLi Zhaoming24-Nov-04 18:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 26 Jun 2002
Article Copyright 2002 by Mohit Khanna
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid