LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Programming a plugin system... (https://www.linuxquestions.org/questions/programming-9/programming-a-plugin-system-542603/)

juanbobo 04-02-2007 05:47 AM

Programming a plugin system...
 
In theory and practice, how do you implement a plugin system for a program in a language like C++? Do you create virtual functions in the main program to be overridden by a library?

Also, libraries are not language-specific, right? So how do you call libraries created in assembly in C++ or vice-versa?

reddazz 04-02-2007 05:55 AM

Moved: This thread is more suitable in the programming forum and has been moved accordingly to help your thread/question get the exposure it deserves.

juanbobo 04-03-2007 05:26 AM

I found a decent site with some information on how to create a plugin system in case anyone else is trying to do the same thing...

http://www.nuclex.org/articles/build...n-architecture

compulover 04-04-2007 08:08 PM

Cool thanks for sharing!!!:)

ta0kira 04-05-2007 12:26 PM

I'd also look at the dlopen manpage. In the final shared library, functions are tabled by name and the using program is responsible for casting it to the correct function prototype. Unfortunately with C++ it has to mangle the function names to account for operators and overloads, but ld knows how to deal with that for a hard coded link.

How I would make a plug-in system (in general) is making the plug-ins shared libs with specific required functions that the using program will dlopen as required. If written in C++ you will need to declare the interface functions with 'extern "C"' so that you can use their real names when dynamically loading them.

As far as the actual interface and methodology I think you are on the right track with that site.
ta0kira

juanbobo 04-06-2007 03:51 AM

Thanks for the help ta0kira, I really appreciate it! One question I have though...isn't it necessary use "export" before the function declarations? I know that gcc does not support this.

ta0kira 04-07-2007 02:25 PM

Well, I would explicitly declare them extern. Here is a simple example of everything together (using gcc):
Code:

//FILE: program.hpp: used as an include for a plug-in

struct PlugIn //abstract plug-in interface
{
  virtual void displayName() const = 0;
};

extern bool RegisterPlugIn(PlugIn*); //called by user's lib to register plug-ins

extern "C"
{
  extern bool InsertPlugIns(); //implemented in the user's lib as an entry point
}

Code:

//FILE: program.cpp: the main program

#include "program.hpp"

//these are defined in the plug-in manager library
//(they aren't in program.hpp because the plug-ins don't need them)
extern void OpenPlugIn(const char*);
extern void DisplayPlugIns();
extern void ClearPlugIns();

int main(int argc, const char *argv[])
{
  for (int I = 1; I < argc; I++)  OpenPlugIn(argv[ I ]);
  DisplayPlugIns();
  ClearPlugIns();
}

Code:

//FILE: plugin-manager.cpp: used to create a shared lib to manage plug-ins

#include <iostream>
#include <vector>
#include <dlfcn.h>
#include "program.hpp"

//list of plug-ins
static std::vector <PlugIn*> PlugInList;

//used only by main program-----------------------------------------------------
extern void OpenPlugIn(const char *nName)
{
  typedef bool(*EntryPoint)();

  //open the plug-in file_______________________________________________________
  std::cout << "opening plug-in file: " << nName << "\n";

  void *File = dlopen(nName, RTLD_NOW);

  if (!File)
  {
  std::cout << "invalid plug-in file (";
  std::cout << dlerror() << ")\n";
  return;
  }

  //obtain the insertion function_______________________________________________
  EntryPoint Execute = (EntryPoint) dlsym(File, "InsertPlugIns");

  if (!Execute)
  {
  std::cout << "invalid plug-in file (";
  std::cout << dlerror() << ")\n";
  return;
  }

  //execute insertion___________________________________________________________
  std::cout << "executing insertion\n";

  if (!(*Execute)()) std::cout << "failed insertion\n";
  else              std::cout << "completed insertion\n";
}


extern void DisplayPlugIns()
{
  std::cout << "plug-in list:\n";
  for (int I = 0; I < PlugInList.size(); I++)
  {
  std::cout << " plug-in " << I << ": ";
  if (PlugInList[ I ]) PlugInList[ I ]->displayName();
  }
}


extern void ClearPlugIns()
{
  std::cout << "plug-in removal:\n";
  for (int I = 0; I < PlugInList.size(); I++)
  {
  std::cout << " remove plug-in " << I << ": ";
  if (PlugInList[ I ]) PlugInList[ I ]->displayName();
  delete PlugInList[ I ];
  PlugInList[ I ] = NULL;
  }

  PlugInList.resize(0);
}


//used by the plug-in-----------------------------------------------------------
bool RegisterPlugIn(PlugIn *pPlugin)
{
  if (!pPlugin)
  {
  std::cout << "  invalid plug-in\n";
  return false;
  }

  std::cout << "  registering plug-in: ";
  pPlugin->displayName();
  PlugInList.push_back(pPlugin);

  return true;
}

Code:

//FILE: my-plugin.cpp the user's plug-in to be used

#include <iostream>
#include "program.hpp"

struct MyPlugIn1 : public PlugIn
{
  void displayName() const
  { std::cout << "[MyPlugIn1]\n"; }
};

struct MyPlugIn2 : public PlugIn
{
  void displayName() const
  { std::cout << "[MyPlugIn2]\n"; }
};

struct MyPlugIn3 : public PlugIn
{
  void displayName() const
  { std::cout << "[MyPlugIn3]\n"; }
};

bool InsertPlugIns()
{
  std::cout << " [loading my-plugin.so]\n";
  if (!RegisterPlugIn(new MyPlugIn1)) return false;
  if (!RegisterPlugIn(new MyPlugIn2)) return false;
  if (!RegisterPlugIn(new MyPlugIn3)) return false;
  return true;
}

Code:

(build and run)

~/plugin-test> g++ plugin-manager.cpp -fPIC -shared -o libprogram.so -ldl #compile shared lib
~/plugin-test> g++ program.cpp -o program ./libprogram.so #compile main program
~/plugin-test> g++ my-plugin.cpp -fPIC -shared -o my-plugin.so ./libprogram.so #compile user plug-in

~/plugin-test> ./program ./my-plugin.so #run the program

ta0kira

ta0kira 07-18-2007 11:56 PM

Sorry, need to add a virtual destructor to PlugIn! Doesn't affect the example, though.
ta0kira

Ephracis 07-19-2007 05:24 PM

ta0kira, thanks a lot for your example. This cleared out a whole lot for me. One question, though: why are you having the plugin-manager as a shared library? Couldn't it just be a normal part of the main program?

ta0kira 07-19-2007 07:27 PM

A good practice is to compartmentalize most of your functionality away from the main program. This is along the same lines as using private and protected data and functions in a class. This does a few things. One: It makes you define discrete boundaries between functionality, which also lets you do whatever you want behind a well-defined interface to other program units. Two: It allows you to limit access other program units have to internals, allowing for better control over what goes on in that unit and mitigating security weaknesses. Three: It allows you to change out a portion of the program to upgrade or fix a bug without having to relink the entire program.

Additionally, linking the plug-in to the shared library makes it complete: it should not require any external linkage in order to be properly loaded. This can allow you to load the plug-in library in the same way you do the plug-in:
Code:

//add this to the top of plugin-manager.cpp

extern "C"
{
  void OpenPlugIn(const char*);
  void DisplayPlugIns();
  void ClearPlugIns();
}

Code:

//FILE: program2.cpp: the alternate main program

#include <dlfcn.h>

int main(int argc, const char *argv[])
{
  void *File = dlopen("./libprogram.so", RTLD_NOW);

  void(*OpenPlugIn)(const char*) = (void(*)(const char*)) dlsym(File, "OpenPlugIn");
  void(*DisplayPlugIns)()        = (void(*)())            dlsym(File, "DisplayPlugIns");
  void(*ClearPlugIns)()          = (void(*)())            dlsym(File, "ClearPlugIns");

  for (int I = 1; I < argc; I++)  (*OpenPlugIn)(argv[ I ]);
  (*DisplayPlugIns)();
  (*ClearPlugIns)();
}

[Re]compile the shared library and the plug-in as shown in my other post and compile the new program like this:
Code:

~/plugin-test> g++ program2.cpp -o program2 -ldl
~/plugin-test> ./program2 ./my-plugin.so

The plug-in and the plug-in manager are compatible with both programs (enclose the externs with extern "C" to make the original one work,) but the second one can utilize it without ever having to link to it. It only needs to link to libdl, which is the dynamic loader. This means it can change between any number of plug-in interfaces itself at run-time.

The demo above isn't exactly a reason to use a shared library, but it can be an added advantage for debugging purposes or allowing a program to be fully-linked before the internals of the plug-in system are devised. It also allows for config files, etc., but the main reasons are the ones I mentioned at the beginning.
ta0kira


All times are GMT -5. The time now is 01:12 PM.