Plug-in Architecture Framework for Beginners

Environment: VC6

In a typical application, a design is implemented that meets the set of requirements at the time of development. Often, after a program is delivered, the user will want added functionality, or different users will require custom functionality based on their specific needs. In order to accommodate these situations without a complete re-write, or causing a develop/compile/test/ship scenario, a framework that allows for future additions of modules without breaking the existing code base needs to be implemented. A plug-in architecture will meet these needs.

What is a plug-in architecture? Simply speaking, it is a framework that will allow a program to “look for” add-in functionality at startup, and then allow that plug-in to cooperate with itself. Many applications, such as Microsoft Office, currently use similar technologies to allow third-party developers to integrate with their existing applications to add functionality or robustness otherwise missing from the application.

How can you make a plug-in architecture model work for you? A very simple way to implement plug-ins is to create an application that will use DLLs to access the plug-in’s functionality. When the app starts, it looks to a pre-determined directory for any DLLs that have the desired functionality. After querying these DLLs, the program can call methods based on a generic interface to which
all the DLLs subscribe.

The sequence of events are as follows.

  1. Program initialized.
  2. On_Init looks for DLLs in a given directory, for example, plug-ins and so forth.
  3. Program calls a method named “load” on each proper DLL found.
  4. After load is called, program caches the name of each module and creates a reference so that the functions in this plug-in can be called later.
  5. During the course of running the program, the user selects one of the menu items, and one of a set of clearly defined standard methods are invoked.
  6. Upon shutdown of program, a method called “unload” will be called to free the resources, which were allocated in the “load” method.

Here is an example of a C++ Plug-in framework.

To make things quite simple, design a DLL with the Visual Studio DLL wizard. Make an export to a function called fnPlug1 that takes no parameters, but returns an integer as follows.

#define PLUG1_API __declspec(dllexport)
extern "C" PLUG1_API int fnPlug1(void);

Now, let’s give the DLL something to do so that we can see that it worked.

Add the implementation to your DLL function as follows.

PLUG1_API int fnPlug1(void)
{
  return 1234;
}

Of course, in a real-world scenario, you would want to make something more robust than just returning a static integer.

In order to get this DLL to behave as a plug-in, we will need to set up a client program that will drive that process. Our goal will be to find all the DLLs, call LoadLibrary() on them, and store the resulting HMODULE so that we can refer to it later.

Here is that example. (Note that we are using the extension .PLX instead of .DLL.)

void CPluginDriverDlg::OnLoad()
{
  char filepath[MAX_PATH];

  //who are we really? Get the Exe Path
  GetModuleFileName(AfxGetApp()->m_hInstance,filepath,
MAX_PATH-1);
  SetCurrentDirectory(ExtractFilePath(filepath));
  CFileFind finder;
  CString strWildCard = _T("*.plx"); //look for the
plugin files

  //call this to set up the finder to iterate through all
  //the plugins
  BOOL bWorking = finder.FindFile(strWildCard);
  while (bWorking)
  {
    //have to call 
FindNextFile() before GetFileName() or GetFilePath()
    //because FindFile just sets the object up and returns
    //true if _ANY_ files were found
    bWorking = finder.FindNextFile();

    HMODULE hm = LoadLibrary(finder.GetFilePath());
    if ( !hm )
    {
      MessageBox("couldn't load");
    }
    else
    {    //loaded OK, so add each library's HMODULE to an array.
      //m_dwa is an MFC CDWordArray
      m_dwa.Add((DWORD)hm);
    }
  }
}

Then, when you want to iterate through your plug-ins, you refer back to the HMODULE that you stored (for each plug-in) and after getting the address of the function, you call that function.

void CPluginDriverDlg::OnRunPlugins()
{
  for(int i=0; i<m_dwa.GetSize() ; i++)
  {
  //Find a function and use it
    PFUNC pFunc = (PFUNC)GetProcAddress(
    (HINSTANCE)m_dwa.GetAt(i), _T("fnPlug1"));
    if (pFunc != NULL)
    {
      int n = pFunc();
      CString answer ;
      answer.Format("The answer is %d", n);
      MessageBox(answer);
    }
  }
}

In case you were wondering, PFUNC is declared as:

typedef int (*PFUNC)(void);

One thing that you will want to make sure and do is to clean up after yourself and release all of the DLLs from memory. That is accomplished by freeing the previously loaded DLLs with FreeLibrary(). I chose to do that on the DestroyWindow() method of my test dialog.

BOOL CPluginDriverDlg::DestroyWindow()
{
  for (int i=0; i<m_dwa.GetSize() ; i++)
  {
    // Free all the libs we used
    FreeLibrary((HMODULE)m_dwa[i]);
  }

  return CDialog::DestroyWindow();
}

That is really about all there is to it. You can compile as many plug-ins as you like, and place them in the same folder as your executable; then, when you are ready, you invoke the procedure that executes the functions in the plug-ins. Keep in mind that this is a very simple example, and could easily be extended to a more robust model that lets you pick which plug-in you would like to run.

Downloads

Download source and demo project – 46 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read