Archived

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

ANWTH : Interface based plug in systems Part 1

This topic is 5134 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi, I have some time to spend so I think I'll start up a programming speech session called : Articles No-one Wanted To Have (ANWTH) Be welcome to the first topic based on Plug-Ins Systems : Interface based plug in systems Part 1 You can't decline it : You already have thought of an easy and fast way to include new features to your game, your engine or your tools ! You can't tell me you haven't ! Since you already have thought of it, why don't you just do it ? Too much hassle ? Too much work ? Nah, it's easy ! 1. The basis. OS such like Windows already use and provide possibilities to dynamically load code that is executed on demand : It's called DLL. A DLL isn't anything else than a set of functions that are accessible through an interface or by a simple pointer to the function. 1.1 Function definition. The first thing you have to do is to define a prototype of the function you want to use within a given DLL. It doesn't really matter what the parameter list looks like or what type the return value is of. All that matter is that everything you use within the definition of the function is available at the point of definition. You want to use a pointer to a structure as a return pointer ? Define the structure before you use it in the function definition. So the first thing we have to define are the function prototypes that allow the access to the functions in the DLL. Here's such a definition :
typedef bool (__cdecl * CANBEUNLOADED)(void);     
Let's take a look at the different parts of this definition : typedef - a typedef lets you define a new data type name. In our example we want to define a new type called CANBEUNLOADED. bool - our return type should be of boolean type. Now the tricky part : __cdecl - This keyword declares the following variable or function as using the C-Style naming conventions. For a function this means that it effects how the parameters are passed (last parameter is pushed first, and the caller cleans up the stack). * - It's a pointer. CANBEUNLOADED - This is the name of the new data type name. For the declaration the next elements are important because like this we know that we define a function. If we wanted to just define a new data type name, we would have left out the next element. (void) - The parameters to pass. Here we declare that it is void. What have we defined ? We have defined a new data type which is called CANBEUNLOADED and which is a function taking no parameters and returning a boolean. The usage is quite simple too :
// Declare a function.
CANBEUNLOADED TheFunction = NULL;

// Put in a pointer (you got from somewhere).
TheFunction = FunctionPointer;

// Call it.
bool bResult = TheFunction();     
It's as easy as that. 1.2 Exporting the function of a DLL. After we have defined the prototype of our functions, we'll have to put them into a DLL and make them accessible from outside the DLL. This isn't quite difficult either. Here's a small function that, once placed into the source code of an DLL, is accessible from outside the DLL :
extern "C" __declspec(dllexport) bool __cdecl CanBeUnloaded(void)
{
	return (0 == g_refCount);
}     
Most of this code should be self explaining. What's interesting is the function definition : "bool __cdecl CanBeUnloaded(void)" is the declaration of a function that implements the function typedef we have seen in 1.1 . It's a function called CanBeUnloaded which takes no parameter ("(void)") and which returns a boolean. __cdecl specifies once more that this function is using the C-style naming conventions. The new part is "extern "C" __declspec(dllexport)". In fact, these are two parts : extern "C" is used to enable C++ programs to make calls to functions that are declared as C functions. In this way it is possible to make the same call from a C and a C++ function to the function declared extern "C". __declspec(dllexport) tells the compiler that I want this function to be exported from the DLL. This is what really enables me to see and access the function from outside the DLL. 1.3 Dynamically loading a DLL. Now that we have defined and exportet the function, we can access it by getting it's pointer out of a DLL. To do so, we'll dynamically load the DLL at run time. What does that mean ? Everytime you create a DLL the compiler also create a lib file which you can link to your programm. That lib file directly gives access to the functions exported from the DLL. Unfortunately this also directly binds your program to the DLL. You can't start-up without loading it and it's impossible to distribute your application without that DLL. A way to avoid this direct link with the DLL is to ignore the lib file and not to link against it. This is the only way to enable a dynamic DLL loading. NOTE : Pay attention to the dependency settings in your development enviroment. At least the Microsoft Visual Studio packages link to the lib files once you have set a dependency to a DLL. To "unlink" from the DLL's lib file, just remove the dependency. Loading a DLL in fact is almost as easy as making a new type definition We just have to pay attention to one or two little things. The first thing is that you should have a mechanism that enables you to load a DLL in ie. a subfolder. You could set a subfolder which contains ie. all your DLLs. By doing this you're sure that windows can resolve any dependencies between the DLLs in that subfolder. This is quite handy because like this you don't need to specify the entire path to the DLL when loading it. Windows has some nice functions to do so. They're called SetCurrentDirectory and GetCurrentDirectory. The usage is quite simple, too :
char sOldWorkingFolder[MAX_PATH];
GetCurrentDirectory(DIM_OF(sOldWorkingFolder), sOldWorkingFolder);
SetCurrentDirectory(_psLibFolder);     
Later on (after you have loaded the DLL) you set the current directory back to the old one :
SetCurrentDirectory(sOldWorkingFolder);     
In between you load the DLL. Windows has a nice function for that, too : LoadLibrary. The usage :
HINSTANCE libInstance;
libInstance = LoadLibrary(_psLibPath);     
_psLibPath is the path and filename of the library (DLL) to open. If Windows was able to open it, you'll get a valid HINSTANCE. If not the returned value is 0x00000000. That's it... the library (DLL) has been dynamically loaded. 1.4 Getting pointer to functions. What we now want is to get the pointer of the functions out of the DLL we just have opened. That's not difficult. All you need is one function and two parameters :
CANBEUNLOADED pfCanBeUnloaded = NULL;
pfCanBeUnloaded = (CANBEUNLOADED) GetProcAddress(libInstance, "CanBeUnloaded");     
We use the in 1.1 defined function CANBEUNLOADED. The above line tries to get the pointer to the function "CanBeUnloaded" located in the DLL whose handle is stored in libInstance. Since the function returns the pointer to the function we simply cast it and store it in our function pointer variable. If the function fails, the return value is NULL so always check if you got a valid pointer before making any call. The first GetProcAddress parameter is always the handle to the instance of the loaded DLL. The second parameter is the name of the function to load. NOTE : Pay attention to the spelling of the function name. It must be *exactly* the same you've used to export the function from the DLL. If you make a mistake (ie. lower char instead of upper char) then the function pointer will not be found. 1.5 Free the DLL. If you don't need the DLL anymore, you'll have to unload it. Don't do this when you want to continue the functions you retrieved from the DLL ! The pointer will be invalid and any call to them will result the computer's self destruction... well... not quite... but it won't work and put up an annoying error message. To free a DLL you only need the DLL libInstance value you got by calling the LoadLibrary function.
FreeLibrary( libInstance );     
That's all... nothing more to do. See you in part two, when we will learn which information we need for a plug-in system and what interfaces we have to create. Any feedback is appreciated, Metron Edit : formating [edited by - Metron on November 27, 2003 9:06:34 AM]

Share this post


Link to post
Share on other sites
Hello there,

this is quite a good subject so I'll post something about cross-platform compatibility here.
I'll try to annotate the following chapters as well so that the fellow Linux and maybe MacOS X developers around can use this series to learn about interface based plugin systems.

If you read the article and you are not using Windows, you have probably been wondering how to implement the described stuff under your favorite UNIX-based OS :D

There are of course equivalent functions across all OS.
The functions on Windows described earlier were:

HINSTANCE LoadLibrary(char*);
void* GetProcAddress( HINSTANCE, char* );
void FreeLibrary( HINSTANCE );

Their Linux equivalents are:

void* dlopen(const char* filename, int flag);
void* dlsym(void* handle, const char* symbol);
int dlclose(void* handle);

As you can see, the parameters are basically the same.
There is an additional 'flag' parameter in the dlopen function.
It can be either RTLD_LAZY or RTLD_NOW which defines the way that external symbols are resolved.
Apart from that there are only type differences: under Windows you have a type HINSTANCE which is replaced with a void* in Linux but it works the same way.

As for most functions you can look up the documentation typing 'man functionname ' (i.e. 'man dlopen') in a console.

Greetings
Marcus

edit: spelIng

[edited by - wi-Z-art on November 27, 2003 11:38:15 AM]

Share this post


Link to post
Share on other sites