In this post I'll expose a little C++ framework to manage a plugin loading system. I wrote this one some years ago for one of my IRL job projects, and now I'm using it in my 3D engine.
plugin.h
:
class IRenderingPlugin
{
virtual long Init( HWND* p_hwnd ) = 0; // init graphic engine and pass the window handle
virtual void Release( void ) = 0; // release graphic engine (unallocate all resources, ...)
virtual void Draw2DSprite( int posx, int posy, char* image ) = 0; // draw something in 2D ...
};
class IRenderingPlugin
{
virtual long Init( HWND* p_hwnd ) = 0; // init graphic engine and pass the window handle
virtual void Release( void ) = 0; // release graphic engine (unallocate all resources, ...)
virtual void Draw2DSprite( int posx, int posy, char* image ) = 0; // draw something in 2D ...
};
2/ Plugin side : creating an openGL plugin
the
plugin specific code implement the previously defined interface:
OpenGLPlugin.h :
OpenGLPlugin.h :
#include
"plugin.h"
class OpenGLRenderingPlugin : public IRenderingPlugin
{
long Init( HWND* p_hwnd );
void Release( void );
void Draw2DSprite( int posx, int posy, char* image );
}
on linux platform:
#include "OpenGLPlugin.h"
extern "C"
{
IRenderingPlugin* PIFactory( void )
{
return new OpenGLRenderingPlugin;
}
}
on windows platform:
#include "OpenGLPlugin.h"
extern "C"
{
__declspec(dllexport) IRenderingPlugin* PIFactory( void )
{
return new OpenGLRenderingPlugin;
}
}
renderer->Init( wnd ); // graphic
middleware init (openGL here)
In your rendering loop (window repaint) :
renderer->Draw2DSprite( 60, 100, sprite_img );
class OpenGLRenderingPlugin : public IRenderingPlugin
{
long Init( HWND* p_hwnd );
void Release( void );
void Draw2DSprite( int posx, int posy, char* image );
}
OpenGLPlugin.cpp
:
#include "OpenGLPlugin.h"
long Init( HWND* p_hwnd )
{
// put here OpenGL init stuff code
return 0; // everything is OK
}
void Release( void )
{
// put here OpenGL stopping stuff code
}
void Draw2DSprite( int posx, int posy, char* image )
{
// put here specific openGL code to draw a 2D sprite on screen
}
#include "OpenGLPlugin.h"
long Init( HWND* p_hwnd )
{
// put here OpenGL init stuff code
return 0; // everything is OK
}
void Release( void )
{
// put here OpenGL stopping stuff code
}
void Draw2DSprite( int posx, int posy, char* image )
{
// put here specific openGL code to draw a 2D sprite on screen
}
The
final step to create our lib plugin is to export a simple function that
instanciate our specific interface implementation and return a pointer on
that:
on linux platform:
#include "OpenGLPlugin.h"
extern "C"
{
IRenderingPlugin* PIFactory( void )
{
return new OpenGLRenderingPlugin;
}
}
on windows platform:
#include "OpenGLPlugin.h"
extern "C"
{
__declspec(dllexport) IRenderingPlugin* PIFactory( void )
{
return new OpenGLRenderingPlugin;
}
}
If you work under MSVC, the
"extern "C"" statement disable microsoft specific functions
names decoration, making your DLL plugin compliant even for binaries
generated with other compilers than MSVC.
On Windows, While opening
your DLL binary with the tool 'Dependency walker' you can see that your plugin
export only one function : PIFactory()
3/ Main
program side : the plugin manager, heart of the system
The
plugin manager provides method to load a plugin (LoadPlugin()), and a method to
find and execute the DLL exported C function "PIFactory()" (Instanciate()).
The
LoadPlugin() method call specific API functions to load a dynamic library (.so
on linux, .dll on win32) : dlopen() for linux, LoadLibraryA for windows. The
Instanciate() method locate and execute the PIFactory() method exported by our
plugin binary to instanciate the plugin interface specific
implementation...To locate an exported function, on windows we use
GetProcAddress(); on Linux we use dlsym().
Notice that the plugin
manager is a template, because we don't want it to be linked with a specific
interface definition. Finally, notice that this is implemented as a
singleton (no need for multiple instances of plugin manager, only one is
enough).
The following code is compliant for both windows and linux
platforms.
pimanager.h :
#ifndef _PIMANAGER_H_
#define _PIMANAGER_H_
#ifdef WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include <string>
#include <map>
typedef enum
{
PIM_OK,
PIM_OK_PIALREADYLOADED,
PIM_FAIL_PILOADING,
PIM_FAIL_PIUNLOADING,
PIM_FAIL_UNKNOWN,
PIM_FAIL_FACTORYFUNCNOTFOUND,
} PluginManagerStatus;
#define PIFACTORYSYMBOLNAME "PIFactory"
template <typename base>
class CPlugInManager
{
public:
#ifdef WIN32
typedef HMODULE Handle;
#else
typedef void* Handle;
#endif
private:
typedef struct
{
Handle handle;
std::string path;
long refcount;
} PluginInfos;
typedef base* (* Factory)( void );
typedef std::map<std::string, PluginInfos> LibList;
CPlugInManager( void ) { };
~CPlugInManager( void ) { };
static LibList* get_lib_list()
{
static LibList m_libs;
return &m_libs;
}
public:
static PluginManagerStatus LoadPlugin( const char* p_path, Handle& p_handle );
static PluginManagerStatus UnloadPlugin( const char* p_path );
static PluginManagerStatus Instanciate( Handle p_handle, base** p_inst );
};
#include "PIManager_impl.h"
#endif
#define _PIMANAGER_H_
#ifdef WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include <string>
#include <map>
typedef enum
{
PIM_OK,
PIM_OK_PIALREADYLOADED,
PIM_FAIL_PILOADING,
PIM_FAIL_PIUNLOADING,
PIM_FAIL_UNKNOWN,
PIM_FAIL_FACTORYFUNCNOTFOUND,
} PluginManagerStatus;
#define PIFACTORYSYMBOLNAME "PIFactory"
template <typename base>
class CPlugInManager
{
public:
#ifdef WIN32
typedef HMODULE Handle;
#else
typedef void* Handle;
#endif
private:
typedef struct
{
Handle handle;
std::string path;
long refcount;
} PluginInfos;
typedef base* (* Factory)( void );
typedef std::map<std::string, PluginInfos> LibList;
CPlugInManager( void ) { };
~CPlugInManager( void ) { };
static LibList* get_lib_list()
{
static LibList m_libs;
return &m_libs;
}
public:
static PluginManagerStatus LoadPlugin( const char* p_path, Handle& p_handle );
static PluginManagerStatus UnloadPlugin( const char* p_path );
static PluginManagerStatus Instanciate( Handle p_handle, base** p_inst );
};
#include "PIManager_impl.h"
#endif
PIManager_impl.h:
template <typename
base>
PluginManagerStatus CPlugInManager<base>::LoadPlugin( const char* p_path, Handle& p_handle )
{
typename LibList::iterator it = get_lib_list()->find( p_path );
if( it == get_lib_list()->end() )
{
#ifdef WIN32
HMODULE hMod = LoadLibraryA( p_path );
#else
void* hMod = dlopen( p_path, RTLD_LAZY );
#endif
if( hMod == NULL )
{
return PIM_FAIL_PILOADING;
}
else
{
PluginInfos pii;
pii.refcount = 1;
pii.handle = hMod;
pii.path = p_path;
LibList* ll = get_lib_list();
(*ll)[p_path] = pii;
p_handle = hMod;
return PIM_OK;
}
}
else
{
// plugin already loaded
p_handle = it->second.handle;
it->second.refcount++;
return PIM_OK_PIALREADYLOADED;
}
return PIM_OK;
}
template <typename base>
PluginManagerStatus CPlugInManager<base>::UnloadPlugin( const char* p_path )
{
typename LibList::iterator it = get_lib_list()->find( p_path );
if( it == get_lib_list()->end() )
{
return PIM_FAIL_UNKNOWN;
}
else
{
it->second.refcount--;
if( it->second.refcount == 0 ) // si plus aucuns hub ne fait reference a ce plugin
{
PluginInfos pii = it->second;
get_lib_list()->erase( it );
#ifdef WIN32
FreeLibrary( pii.handle );
#else
dlclose( pii.handle );
#endif
}
}
return PIM_OK;
}
template <typename base>
PluginManagerStatus CPlugInManager<base>::Instanciate( Handle p_handle, base** p_inst )
{
#ifdef WIN32
FARPROC proc = GetProcAddress( p_handle, PIFACTORYSYMBOLNAME );
#else
Factory proc = (Factory)dlsym( p_handle, PIFACTORYSYMBOLNAME );
#endif
if( proc == NULL )
{
return PIM_FAIL_FACTORYFUNCNOTFOUND;
}
else
{
Factory factory = (Factory)proc;
base* inst = (*factory)();
*p_inst = inst;
return PIM_OK;
}
return PIM_OK;
}
PluginManagerStatus CPlugInManager<base>::LoadPlugin( const char* p_path, Handle& p_handle )
{
typename LibList::iterator it = get_lib_list()->find( p_path );
if( it == get_lib_list()->end() )
{
#ifdef WIN32
HMODULE hMod = LoadLibraryA( p_path );
#else
void* hMod = dlopen( p_path, RTLD_LAZY );
#endif
if( hMod == NULL )
{
return PIM_FAIL_PILOADING;
}
else
{
PluginInfos pii;
pii.refcount = 1;
pii.handle = hMod;
pii.path = p_path;
LibList* ll = get_lib_list();
(*ll)[p_path] = pii;
p_handle = hMod;
return PIM_OK;
}
}
else
{
// plugin already loaded
p_handle = it->second.handle;
it->second.refcount++;
return PIM_OK_PIALREADYLOADED;
}
return PIM_OK;
}
template <typename base>
PluginManagerStatus CPlugInManager<base>::UnloadPlugin( const char* p_path )
{
typename LibList::iterator it = get_lib_list()->find( p_path );
if( it == get_lib_list()->end() )
{
return PIM_FAIL_UNKNOWN;
}
else
{
it->second.refcount--;
if( it->second.refcount == 0 ) // si plus aucuns hub ne fait reference a ce plugin
{
PluginInfos pii = it->second;
get_lib_list()->erase( it );
#ifdef WIN32
FreeLibrary( pii.handle );
#else
dlclose( pii.handle );
#endif
}
}
return PIM_OK;
}
template <typename base>
PluginManagerStatus CPlugInManager<base>::Instanciate( Handle p_handle, base** p_inst )
{
#ifdef WIN32
FARPROC proc = GetProcAddress( p_handle, PIFACTORYSYMBOLNAME );
#else
Factory proc = (Factory)dlsym( p_handle, PIFACTORYSYMBOLNAME );
#endif
if( proc == NULL )
{
return PIM_FAIL_FACTORYFUNCNOTFOUND;
}
else
{
Factory factory = (Factory)proc;
base* inst = (*factory)();
*p_inst = inst;
return PIM_OK;
}
return PIM_OK;
}
4/ How
to use the plugin manager in your main program
To load
a plugin and retrieve a pointer to the implemented interface proceed like that
:
//
plugin handle declaration
CPlugInManager<IRenderingPlugin>::Handle pihandle;
// interface pointer
IRenderingPlugin* renderer;
PluginManagerStatus pistatus = CPlugInManager<IRenderingPlugin>::LoadPlugin( "openglpi.dll", pihandle );
if( pistatus != PIM_OK )
{
// something went wrong : cannot find the file, file existe but cannot find the exported func,
return IMFALSE;
}
// now we can instanciate the plugin specific interface implementation:
if( CPlugInManager<IRenderingPlugin>::Instanciate( pihandle, &renderer ) != PIM_OK )
{
return IMFALSE;
}
CPlugInManager<IRenderingPlugin>::Handle pihandle;
// interface pointer
IRenderingPlugin* renderer;
PluginManagerStatus pistatus = CPlugInManager<IRenderingPlugin>::LoadPlugin( "openglpi.dll", pihandle );
if( pistatus != PIM_OK )
{
// something went wrong : cannot find the file, file existe but cannot find the exported func,
return IMFALSE;
}
// now we can instanciate the plugin specific interface implementation:
if( CPlugInManager<IRenderingPlugin>::Instanciate( pihandle, &renderer ) != PIM_OK )
{
return IMFALSE;
}
After
this init phase, you can now call your plugin functions and do the work !
In your rendering loop (window repaint) :
renderer->Draw2DSprite( 60, 100, sprite_img );
Enjoy ! ;)
incompréhensible!
RépondreSupprimerpour moi en tout cas...
anne-ma
quite helpful for me! ( especially windows AND linux implementation)
RépondreSupprimerthanks.
samplay.
you welcome ! :-)
RépondreSupprimerNevil