|
LoadLibrary-GetProcAddress Fun
Submitted by |
I explain a macro trick used by the authors of the BASS sound
library to help with dynamic loading of the DLL; then I introduce a way to
make it even easier to apply. Afterwards I extend the idea with a modified
macro + a header file trick which useful by itself. 3 totds in one! :)
Last night, adding the sound library BASS (visit
http://www.un4seen.com/music/) to a pet project, I noticed they had added
and documented a nice trick to help people who want to load the DLL at
runtime instad of static-linking it. The trick is in the way all the
functions in the DLL's header file bass.h are declared:
BOOL BASSDEF(BASS_GetDeviceDescription)(int devnum, char **desc); |
i.e. with the macro BASSDEF surrounding the function name. This macro is
defined at the beginning:
#ifndef BASSDEF
#define BASSDEF(f) WINAPI f
#endif
// The declaration above is, after the macro substitution, a plain old
function declaration:
BOOL WINAPI BASS_GetDeviceDescription(int devnum, char **desc); |
Normally, if you want to load a DLL at runtime and GetProcAddress() the
functions you want, you are forced to declare your own function pointer
vars with the right parameter declaration. However, in BASS, when you
include the header file, you can simply do
#define BASSDEF(f) (WINAPI *f) // define the functions as pointers
#include "bass.h"
// The declaration above is now interpreted differently:
BOOL (WINAPI *BASS_GetDeviceDescription)(int devnum, char **desc); |
and instead of the useless (for dynamic loading) function declarations, you
have obtained a list of function pointer variables with the right
parameters. Cool! Now it's up to you to write:
BASS_GetDeviceDescription = GetProcAddress(bass,
"BASS_GetDeviceDescription"); |
for each function you want, and then call BASS_GetDeviceDescription(-1,
&pInfo) just like you normally would. What a cool tip of the day, right?
WRONG! You missed type checking. A C++ compiler will complain that
BASS_GetDeviceDescription is of type int (__stdcall *)(int, char**), while
GetProcAddress() returns a int (__stdcall *)(void) value. DAMN! We're right
where we started because we have to typecast each GetProcAddress
separately, and to do so we need the right parameters again.
Instead of that mess, we can do some weird typecast trickery:
*(void**)&BASS_GetDeviceDescription=(void*)GetProcAddress(bass,
"BASS_GetDeviceDescription"); |
Since we already know the funcptr variables have the correct types (because
they come from the official header), we can simply coerce the compiler into
treating them as (void*) variables during the assignment, and avoid
repeating the function declarations. I ended up using a macro to further
simplify things:
#define INITBASSF(f) *(void**)&f=(void*)GetProcAddress(bass, #f)
INITBASSF(BASS_Init);
INITBASSF(BASS_Start);
INITBASSF(BASS_Stop);
INITBASSF(BASS_Free);
INITBASSF(BASS_GetInfo); |
There's even more! We still have to write our list of funcptr
initializations. Should we forget one, the funcptr variable will be
uninitialized, trying to call it may cause major havok. I know this is a
minor burden since you will catch most errors right away in a debug build;
however, can we automate this further? YES! They could have separated the
function declarations into their own header file (without any
single-include guards), with the standard BASSDEF macro taking also the
parameter declaration as a macro parameter:
// Inside "Bass.h"
#define BASSDEF(f,p) WINAPI f p
...
#include "BassFunctions.h"
// BassFunctions.h:
BOOL BASSDEF(BASS_GetDeviceDescription, (int devnum, char **desc));
void BASSDEF(BASS_SetBufferLength, (float length));
//... the rest of the BASSDEF function declarations |
With that little help bit, my MusicPlayer.cpp wrapper file would look
something like this:
#define BASSDEF(f,p) (WINAPI *f) p // define the functions as pointers
#include "Bass.h"
//....
bool CMusicPlayer::Init()
{
HINSTANCE bass=LoadLibrary("BASS.DLL"); // load BASS
if (!bass) return false;
#undef BASSDEF
#define BASSDEF(f,p) *(void**)&f=(void*)GetProcAddress(bass, #f)
#include "BassFunctions.h"
// Your initialization here....
} |
And there you have it. No maintenance burden, no typing mistakes, no need
to repeat a list of names. With the original BASS header file built the way
I propose, you only need 6 lines of code to switch from static to dynamic
linking.
I have used the "declarator macro + listing include file" technique quite a
few times, in situations well unrelated to DLLs, Windows, Visual C or games
altogether, so I guess any C++ programmer can benefit from it. :) For
instance, you have a series of enumerated type constants and you want to
have them available also in string form (say, to read the constant in
string form from a text script file):
// UnitIdList.h
DECLARE_ID(UNITID_SOLDIER)
DECLARE_ID(UNITID_WORKER)
DECLARE_ID(UNITID_MACHINE)
DECLARE_ID(UNITID_RESOURCE)
// UnitId.h
//...
#define DECLARE_ID(a) a,
enum TUnitId
{
#include "UnitIdList.h"
UNITID_INVALID
};
//...
// somewhere in my cpps...
#include "UnitId.h"
#undef DECLARE_ID
#define DECLARE_ID(a) { a, #a },
static struct { int id; const char *pszId; }
s_aUnitIds[] =
{
#include "UnitIdList.h"
};
TUnitId FindUnitIdFromStringID(const char *pszId)
{
for (int i = 0; i < ARRAY_LEN(s_aUnitIds); i++)
if (0 == stricmp(s_aUnitIds[i].pszId, pszId))
return s_aUnitIds[i].id;
return UNITID_INVALID;
} |
Javier Arevalo (jare@jet.es, http://web.jet.es/jare)
|
The zip file viewer built into the Developer Toolbox made use
of the zlib library, as well as the zlibdll source additions.
|