Note: this article first appeared as a Tip Of The Day in Flipcode.
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;
}