Archived

This topic is now archived and is closed to further replies.

TonyFish

Plugin Systems

Recommended Posts

Hi I was thinking about writing a dll plugin system a la Half Life but I''m not sure how to go about it. Any pointers from you veterans would sure be appreciated! Thanks

Share this post


Link to post
Share on other sites
Hey, I'm no veteran... no more than a mere hobbyprogrammer like you. But check out my post in this thread:

my post is nr.9

If you need a better explanation of what I mean just tell me...

[edited by - bilsa on April 5, 2004 2:55:51 PM]

[edited by - bilsa on April 5, 2004 2:56:17 PM]

Share this post


Link to post
Share on other sites
just thought I''d be a bit more precise. I''d like to make something two-way so that modules can call engine functions. Callbacks/Functors seem to be the way to go but my query lies with the general structure of the system. Some words of wisdom from those of you who have already designed similar mechanisms would be nice.
Thanks

Share this post


Link to post
Share on other sites
To implement a plugin system like that you will have to expose functions from your engine as Function Pointers.

Here is an example:

Engine Header

int __stdcall GetNodeSize( void );
void __stdcall SetHeight( int x , int y , float value );
float __stdcall GetHeight( int x , int y );
unsigned char* __stdcall GetTextureData(int w, int h);
char* __stdcall GetCurrentFileName();


Engine Source

int __stdcall GetNodeSize( void )
{
...
}
void __stdcall SetHeight( int x , int y , float value )
{
...
}
float __stdcall GetHeight( int x , int y )
{
...
}
unsigned char* __stdcall GetTextureData(int w, int h)
{
...
}
char* __stdcall GetCurrentFileName()
{
...
}


Engine Plugin Loader

class CPlugin
{
public:
// Constructor

CPlugin()
{
m_hLibrary = 0;
pInfo = NULL;
pInterface = NULL;
pluginLoaded = false;
pluginName = "";
}
// destructor

~CPlugin()
{
if( !pluginLoaded )
return;

delete pInterface;
delete pInfo;
::FreeLibrary( m_hLibrary );
}
// Load the plugin and return it

bool Load( const char* szName)
{
bool success = false;
pluginName = szName;
// load pluggin

m_hLibrary = LoadLibrary( szName );
// handle errors

if( m_hLibrary == NULL )
return success;
// get the address of our function

getPluginInfo = (GETINFO)GetProcAddress(m_hLibrary,"
GetInfo");
runPlugin = (RUN)GetProcAddress(m_hLibrary,"
Run");
// get our graphics device

if( getPluginInfo )
{
pInfo = new PluginInfo;
getPluginInfo( pInfo );
}
if(pInfo != NULL && runPlugin)
{
pInterface = new Plugin;
pInterface->GetNodeSize = ::GetNodeSize;
pInterface->SetHeight = ::SetHeight;
pInterface->GetHeight = ::GetHeight;
pInterface->GetTextureData = ::GetTextureData;
pInterface->GetCurrentFileName = ::GetCurrentFileName;
success = true;
pluginLoaded = true;
}
return success;
}
// Load the plugin and return it

void Run(int index)
{
if(pInfo != NULL)
{
if(pInterface != NULL)
{
if(runPlugin != NULL)
{
runPlugin(pInterface,index);
}
}
}
}

AnsiString GetName()
{
return pluginName;
}
PluginInfo* pInfo;
Plugin* pInterface;
private:

AnsiString pluginName;
bool pluginLoaded;

RUN runPlugin;
GETINFO getPluginInfo;

// platform specific

HINSTANCE m_hLibrary;
};


Plugin Header

#ifndef CPLUGINH
#define CPLUGINH
//-------------------------------------------

// Includes

//-------------------------------------------

#ifdef __cplusplus
extern "C" {
#endif
//-------------------------------------------

// Defines

//-------------------------------------------

#define VERSION 1000
#define IMAGEFILEIMPORT 0
#define IMAGEFILEEXPORT 1
#define HEIGHTMAPFUNCTION 2
#define MODELFILEIMPORT 3
#define MODELFILEEXPORT 4
#define HEIGHTMAPIMPORT 5
#define HEIGHTMAPEXPORT 6

// Info

typedef int( __stdcall *GETNODESIZE )( void );
// Heightmap Functions

typedef void ( __stdcall *SETHEIGHT )( int x, int y, float value);
typedef float ( __stdcall *GETHEIGHT )( int x , int y );
// Texture Functions

typedef unsigned char* ( __stdcall *GETTEXTUREDATA )( int w , int h );
typedef char* ( __stdcall *GETCURRENTFILENAME )();

// Plugin Interface

typedef struct
{
GETNODESIZE GetNodeSize;
SETHEIGHT SetHeight;
GETHEIGHT GetHeight;
GETTEXTUREDATA GetTextureData;
GETCURRENTFILENAME GetCurrentFileName;
}Plugin;

typedef struct
{
char FunctionName[ 50 ];
char Extension[3];
char Author[ 100 ];
char Info[256];
char FileDescriptor[50];
int FunctionType;
}FunctionDescriptor;

//this will be filled in by the dll:

typedef struct
{
int NumberOfFunctions;
FunctionDescriptor * FunctionDescriptors;
int InterfaceVersion;
}PluginInfo;


//these functions must be implemented in the plugin dll:

void __declspec(dllexport) __stdcall Run( Plugin *Interface , int FunctionIndex );
void __declspec(dllexport) __stdcall GetInfo( PluginInfo *DLLInfo );


typedef void ( __stdcall *RUN )( Plugin * , int );
typedef void ( __stdcall *GETINFO )( PluginInfo *);
#ifdef __cplusplus
}
#endif

#endif


Plugin Source

//----------------------------------------------------

// Includes

//----------------------------------------------------

#include <windows.h>
#include "Plugin.h"
#include "string.h"

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//---------------------------------------------------------------------------

void __stdcall PluginRoutine( Plugin *Interface )
{
char filename[256];
strcpy(filename,Interface->GetCurrentFileName());
unsigned char *data = Interface->GetTextureData(bitmap->Width,bitmap->Height);
...
}
void __stdcall Run( Plugin *Interface , int FunctionIndex )
{
if ( FunctionIndex == 0 )
{
PluginRoutine( Interface );
}
}
void __stdcall GetInfo( PluginInfo *DLLInfo )
{
DLLInfo->NumberOfFunctions = 1;
DLLInfo->FunctionDescriptors = (FunctionDescriptor*)malloc(sizeof(FunctionDescriptor)*DLLInfo->NumberOfFunctions);

//place the user data into the descriptors

strcpy( DLLInfo->FunctionDescriptors[0].FunctionName , "PluginRoutine" );
strcpy( DLLInfo->FunctionDescriptors[0].Author , "<Author>" );
strcpy( DLLInfo->FunctionDescriptors[0].Info , "<Do work on data calling functions from engine>" );
strcpy( DLLInfo->FunctionDescriptors[0].Extension , "" );
strcpy( DLLInfo->FunctionDescriptors[0].FileDescriptor , "
" );
DLLInfo->FunctionDescriptors[0].FunctionType = IMAGEFILEIMPORT;

}


For more information check out this link:
http://www.function-pointer.org/

Share this post


Link to post
Share on other sites
Thanks!! (sarcasm)

It was just quick and dirty!

I guess now I am forced to explain

Basically what is being done in the code above is that the actual functions you are planning to expose are just regular c style functions. To access these from a plugin you create a pointer to each of those functions that is declared in the plugin header file in the form of:

typedef return_value ( __stdcall *POINTER_TO_FUNCTION)( variables to be passed);

You then create a struct that encapsulates the function pointers that you declared (above) so that you only need to reference the structure to gain access to all of the exposed functions.

In the actual plugin you create two functions and then pointers to those functions that will allow access to all of the funhctions that the plugin will use when it runs. So one function is used to get information about the various plugin functions and the other you use to actually run the requested function. This way you have the flexibility within the plugin to have multiple functions that do various things without having to modify the actual engine code needed to run those plugins.

The result of this is that when loading a plugin you will be given information about what that plugin does, then based on that information it will allow you to call the functions within the plugin from your engine.

The CPlugin class encapsulates the plugin loading code so you only have to write it once and it can be used to load all of the plugins availiable for your application. All this class does is calls the windows api functions to load and unload a dll and associates the engine functions with the plugin''s pointers to the functions that are inside the Plugin structure.

So for example if you want to expose a function that allws access to heightmap data for a terrain engine you would expose a function that returns a pointer to the heightmap data. You then in a plugin header file declare a pointer to the above function and include a declaration of that pointer in a structure that is accessable to the engine. The reason it is accessable to the engin is that you have set aside 2 functions that the engine knows will always exist. One to explain the process you plan to do and one to call those processes. In the plugin c or cpp file you write a function that modifies the heightmap data and explain this function if the GetInfo function that the engine knows about. In the actual function that modifies the data you call the function pointer to attain a pointer to the data and modify it thus modifying it in the engine. Last of all during runtime you load the plugin and run the function you created to modify the heightmap data. Done!

Share this post


Link to post
Share on other sites
What we do, is make EVERYTHING an interface. There are very few global functions in our source.

Then, we stuff many of these interfaces (really, just abstract C++ class declarations) into a big Environment, which is just a hash table from some given name, to a void pointer. This Environment gets passed around to all factory functions, such that newly made objects can find what they need in the environment.

Because virtual calls work across DLL boundaries, as long as you don''t change your class lay-out, I can call some factory that lives in a DLL, and pass it my environment; that class can pull out my texture manager (or whatever) and call it back.

The really cool thing is that, because we have no globals, we can create one environment per thread, or create multiple virtual applications within the same address space.

Factories, Managers, Services and Systems go in the environment. Specific instances (Entity, etc) don''t go in the environment, but the EntityManager that you can use to get at all entities, does.

Share this post


Link to post
Share on other sites
Not sure about platform specifics, but an approach that seemed to work in some circumstances was:

- Create a base class for your plugin class (which itself could be a factory for other objects). This will definitely have virtual methods.
- Create base classes for whatever other classes you want the plugin to be able to override (with virtual methods)

- In your DLL, publish a single function which is a factory function for an object which inherits the base class
- Create a single one of this object then use it as a factory for everything else.

This essentially gets around dodgy C++ linkeages or being unable to call constructors from a DLL (which I think must be tricky at best).

The only thing I found with this method, was that on Linux with g++, you have to use a nonstandard linker option (-Wl,--export-dynamic) to ensure that some symbols are exported from the exe (yes, FROM the exe TO the DLLs). Otherwise a linker error occurs at runtime.

Apart from that you''re fine.

Mark

Share this post


Link to post
Share on other sites
As I explained in my post, if you make a Systems class which exposes a GetModule(string moduleID), and a GetActiveSystem(string moduleType) to every DLL... then this dll can do whatever it wants to... The client application simply consists of the Systems class.

Then for eg.

I have loaded all the module I wanted at first. Then inside my GLRenderer.dll i can do like this:

LogManagerInterface* logger;
GetActiveSystem("LogManager", (void**)&logger);

And tada... you can use the LogManager inside this module.
This way you will only need to export one or two functions to each module and the module in turn will only need to export one function.

sorry todd, but what toddgrayson explained isn't actually any good idea...

believe me... you dont want to export every function like that...
Think what happens when you add a crucial function in your client application. Now think of the hell you will have exporting it to each of the 50 modules you have...

Now If you dont like the Interface ideas. I would go for making a FunctionPool (can be as simple as a vector...) where you register all the functions you want to export. And then you just export a function that retrieves this FunctionPool whenever called:

FunctionPool* fnPoolExport = ...register all functions to export...

//This is the function you need to export to every module...
FunctionPool* getFunctions() {
return fnPoolExport;
}



[edited by - bilsa on April 5, 2004 6:59:59 PM]

Share this post


Link to post
Share on other sites
Sorry,

I was mistaken in what you were trying to accomplish. My thoughts were along the line of writing a plugin system that would be supported via an SDK where you only want to expose a limited portion of the functionality to the end user as they would not have access to the actual code involved. Here you don''t want to expose each class involved though writing a class with virtual functions and having the end user extend that class would work as well if not better.

Share this post


Link to post
Share on other sites