Thread: Cross platform plugin architecture

  1. #1
    Registered User
    Join Date
    Aug 2010
    Posts
    4

    Cross platform plugin architecture

    Hi all,

    (Very) junior C++ developer here. There's something I've been thinking of trying, but am really unsure how to go about it. I'm thinking along the lines of having a main binary in c++ which contains a base 'plugin' class, which may be subclassed to fill in implementation details for various plugins. So far so good.

    But ideally what I would like would be to load up the subclass at runtime to cut down the size of the main program, accessing members through references to the superclass in the main code. Would I have to produce libraries for each platform? If so where could I find out how to do this? I'm sure this is very naive and not nearly as simple as I would hope, but I'd really appreciate any pointers in the right direction to some relevant articles perhaps, I'm having trouble finding anything useful with Google.

    Thanks for reading, and hello btw (my first post)
    Cheers!

  2. #2
    Frequently Quite Prolix dwks's Avatar
    Join Date
    Apr 2005
    Location
    Canada
    Posts
    8,057
    I became very interested in doing this myself at one point, although I was using C at the time and not C++. I can tell you a bit about what to expect.

    First of all, this is quite platform-dependent. You'll probably be able to create a dynamic library scheme that works on Linux and other UNIX-like environments, and just as probably you'll have to use DLLs if you want the scheme to also work under Windows. I know next to nothing about how this works under Windows, so someone else will have to fill you in there, but the way it works in Linux is like this.

    Linux supports static libraries, which are added to your executable at compile time and actually reside there, and dynamic libraries, which are referred to by your executable and must be loaded at run-time. Dynamic libraries can be loaded by the linker when your program is loaded, which is (often) how code like the standard C and C++ libraries are included; or, you can call dlopen() yourself and try to load the library. (Short tutorial: Shared Library Mini-Tutorial) Once you've opened the library with dlopen() you can iterate through the functions and symbols defined in it, or call some function with a predefined name.

    The details on how to do this in C++ I'm not so familiar with, but when I did try it I found this HOWTO very useful:
    C++ dlopen HOWTO: C++ dlopen mini HOWTO
    Part specific to loading classes: C++ dlopen mini HOWTO

    So in short, it's not as easy as it sounds (of course), but if you can get through that HOWTO you can certainly figure it out. It can lead to a very nice plugin system if you do.

    Finally, I'd strongly suggest that you write your application in such a way that plugins can be compiled with your main program as well, because the dynamic library loading part may be difficult to get working all of the time . . . .

    * * *

    One last thing: here's some C code where I coded shared library loading for plugin support. (Link: Public Git Hosting - xuni.git/blob - src/test/main.c)
    Code:
     123 #ifdef LOADSO_STATIC_VERSION
    124 func_point_t xuni_loadso_load_function(loadso_t object, const char *func) {
    125     struct string_function_t data[] = {
    126         {"game_click", (func_point_t)game_click},
    127         {"game_event", (func_point_t)game_event},
    128         {"game_free", (func_point_t)game_free},
    129         {"game_init", (func_point_t)game_init},
    130         {"game_paint", (func_point_t)game_paint},
    131         {"game_start", (func_point_t)game_start},
    132         {"menu_click", (func_point_t)menu_click},
    133         {"menu_event", (func_point_t)menu_event},
    134         {"menu_free", (func_point_t)menu_free},
    135         {"menu_init", (func_point_t)menu_init},
    136         {"menu_paint", (func_point_t)menu_paint},
    137         {"menu_start", (func_point_t)menu_start},
    138         {"options_click", (func_point_t)options_click},
    139         {"options_event", (func_point_t)options_event},
    140         {"options_free", (func_point_t)options_free},
    141         {"options_graphics_deactivate",
    142             (func_point_t)options_graphics_deactivate},
    143         {"options_init", (func_point_t)options_init},
    144         {"options_paint", (func_point_t)options_paint},
    145         {"options_start", (func_point_t)options_start},
    146         {"options_theme_deactivate", (func_point_t)options_theme_deactivate}
    147     };
    148     func_point_t funcp
    149         = string_to_function(data, sizeof(data) / sizeof(*data), func);
    150     
    151     if(!funcp) {
    152         log_message(ERROR_TYPE_RESOURCE, 0, __FILE__, __LINE__,
    153             "Unknown function: \"%s\"", func);
    154     }
    155     
    156     return funcp;
    157 }
    158 #endif
    By means of macros, I would conditionally compile code to load functions from shared libraries (the shared library code is in loadso.c line 68, but I haven't shown it here since it has both dlopen() and SDL support and hence is a little messy) -- or if shared libraries were not supported, the code would be statically linked into the application and the above function would "cheat" and perform the lookups instead.

    xuni, the application that came from, would actually allow the user to specify in XML files code like
    Code:
    <handler file="src/editor/editor.so">
    and hence add functionality to the application without requiring any recompilation at all . . . .

    Probably it's not worth all the hassle it takes to get something like this working, but it's fun nevertheless.

    * * *

    Many times, programs that want to support plugins like these will allow the plugins to be written in other languages, like Lua or Python (boost has a nice python library). That takes a lot more effort though and I wouldn't recommend trying it at this stage.

    Final thing (I promise this time): there are probably libraries that allow this sort of thing to be done much more simply. I wouldn't be surprised if boost supported it, for example. Unfortunately I don't know of any, though, so you'll just have to wait for other responses.
    Last edited by dwks; 08-17-2010 at 12:50 PM.
    dwk

    Seek and ye shall find. quaere et invenies.

    "Simplicity does not precede complexity, but follows it." -- Alan Perlis
    "Testing can only prove the presence of bugs, not their absence." -- Edsger Dijkstra
    "The only real mistake is the one from which we learn nothing." -- John Powell


    Other boards: DaniWeb, TPS
    Unofficial Wiki FAQ: cpwiki.sf.net

    My website: http://dwks.theprogrammingsite.com/
    Projects: codeform, xuni, atlantis, nort, etc.

  3. #3
    Registered User
    Join Date
    Aug 2010
    Posts
    4

    Thanks!

    Thanks for the detailed reply! Yes I will definitely check out those tutorials.

    I did find a library called DynObj which looks like it might be useful, but haven't got round to trying it out yet.

  4. #4
    Resu Deretsiger Nightowl's Avatar
    Join Date
    Nov 2008
    Location
    /dev/null
    Posts
    186
    For (a little) more detail on how to get this sort thing working in C++ . . .

    Due to Name mangling, C++ function names are changed to a form not easily rememberable. Therefore, instantiating a class is not completely straightforwards. I've found that the easiest way to dynamically load C++ classes is to export a C symbol (extern "C" does this nicely) that instantiates the class.

    Note that I've only used dlopen() and friends on POSIX-based platforms, but I would imagine the process is rather similar on Windows-based platforms. An example:

    (Main header file)
    Code:
    #ifndef Example_H
    #define Example_H
    
    class ExampleClass {
    public:
        ExampleClass();
        virtual ~ExampleClass();
    public:
        virtual void doSomething() = 0;
    };
    
    
    extern "C" {
    /* To be implemented in subclasses. */
    ExampleClass *Instantiate();
    }
    
    #endif
    (Main source file)
    Code:
    #include <dlfcn.h>
    
    int main(int argc, char *argv[]) {
        void *libraryHandle = dlopen(argv[1], RTLD_LOCAL | RTLD_LAZY);
        /* TODO: add error-checking. */
    
        ExampleClass *(*instantiationFunction)();
    
        /* From the man page for dlopen():
         Writing: cosine = (double (*)(double)) dlsym(handle, "cos");
                  would seem more natural, but the C99 standard leaves
                  casting from "void *" to a function pointer undefined.
                  The assignment used below is the POSIX.1-2003 (Technical
                  Corrigendum 1) workaround; see the Rationale for the
                  POSIX specification of dlsym(). */
    
        *(void **)(&instantiationFunction) = dlsym(libraryHandle, "Instantiate");
        /* TODO: add error-checking in here. */
        ExampleClass *instance = instantiationFunction();
    
        instance->doSomething();
    
        delete instance;
    
        dlclose(libraryHandle);
    
        return 0;
    }
    (Plug-in #1 source)
    Code:
    #include <iostream>
    #include "Example.h"
    
    class Plugin1Example : public ExampleClass {
    public:
        Plugin1Example();
        virtual ~Plugin1Example();
    public:
        virtual void doSomething();
    };
    
    void Plugin1Example::doSomething() {
        std::cout << "Plugin1Example::doSomething() . . ." << std::endl;
    }
    
    ExampleClass *Instantiate() {
        return new Plugin1Example();
    }
    And so on. The code should be relatively self-explanitory, but here's a synopsis (in execution order):
    • main() loads the shared library using dlopen(), the library name is the first argument passed to the program.
    • main() looks up a symbol called "Instantiate" within the loaded library. (Note that the extern "C" {} exports all symbols inside the brackets as C symbols (e.g. no mangling.))
    • main() calls the looked-up function, which creates a new Plugin1Example instance and returns it.
    • main() calls Plugin1Example::doSomething() and then deletes the instance.
    • main() deletes the created Plugin1Example instance, frees the library handle, then exits.


    In theory, you should be able to combine this approach with a cross-platform C dynamically-loaded library system as dwks mentioned. However, you would probably be better off using a dedicated library such as DynObj (looks neat, by the way) . . . it would definitely be easier, for one thing.
    Do as I say, not as I do . . .

    Experimentation is the essence of programming. Just remember to make a backup first.

    "I'm a firm believer that <SomeGod> gave us two ears and one mouth for a reason - we are supposed to listen, twice as much as we talk." - LEAF

    Questions posted by these guidelines are more likely to be answered.

    Debian GNU/Linux user, with the awesome window manager, the git version control system, and the cmake buildsystem generator.

  5. #5
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    The simple answer to this question is there isn't one solution that is cross-platform b/c both platforms do it very differently.

  6. #6
    Registered User
    Join Date
    Aug 2010
    Posts
    4
    Excellent, lots to be getting on with here. Thanks so much for all your help.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Plugin System Architecture
    By appleGuy in forum C++ Programming
    Replies: 0
    Last Post: 06-08-2009, 01:09 PM
  2. Cross platform XML library
    By Josh Kasten in forum C++ Programming
    Replies: 2
    Last Post: 04-09-2007, 04:04 PM
  3. load gif into program
    By willc0de4food in forum Windows Programming
    Replies: 14
    Last Post: 01-11-2006, 10:43 AM
  4. Question..
    By pode in forum Windows Programming
    Replies: 12
    Last Post: 12-19-2004, 07:05 PM
  5. cross platform game programming
    By xddxogm3 in forum Game Programming
    Replies: 13
    Last Post: 08-22-2004, 09:40 AM