Disclaimer: I have written the following code from head without a compiler so it is possible that there are a few variable/keywords improperly named. It should not be much difficult for you to correct them but the most important thing for me is that you grasb the idea behind object instantiation from string literals.
An old dream I had when I was a young C++ programmer was to be able to instance objects from strings in C++. So instead of writing pObject = new Class() I would write something like pObject = createInstance("Class"). Apart from having a function like:
void *createInstance(const char *pszString)
{
if(strcmpi(pszString, "Class") == 0)
return new Class();
else
...
}
which is not exactly something that clever. Yes it works but well... we can do better. I will propose two different way of doing.
A first solution is to use DLL exports functions. This is the trick used in the Half-Life SDK but I don't know where it takes its real origin. The idea is to create an interface from which all instantiable objects derive from:
class IInstantiable
{
public:
virtual bool setParam(const char *pszKeyName, const char *pszKeyValue) = 0;
};
The interface has only one pure virtual function whose role is to send parameters to the actual object. This is necessary because we have no internal details about the object specialization so we can only access them through the interfaces methods. You may define other methods to better suit your needs but remember that a good interface should not have a myriad of functions.
In a second step, we create a DLL which contains all the classes that you need to be accessed by the instancing mechanism and we add export function to create these instances. For example:
class Class : public IInstantiable
{
public:
...
};
extern "C" _declspec(dllexport) IInstantiable* createClass(void)
{
return static_cast<IInstantiable*>(new Class());
}
Then, all we have to do is to load the library and use its handle value to get the entry function like:
HANDLE g_hLibrary = INVALID_HANDLE_VALUE;
void load(void)
{
g_hLibrary = LoadLibraryA("somedll.dll");
}
void quit(void)
{
FreeLibrary(g_hLibrary);
}
IInstantiable* createInstance(const char *pszString)
{
if(g_hLibrary == INVALID_HANDLE_VALUE)
return null;
char szTmp[128];
sprintf(szTmp, "create%s", pszString);
void *pEntry = GetProcAddress(g_hLibrary, szTmp);
if(pEntry == null)
return null;
return (*(IInstantiable*(*)(void))pEntry)();
}
The writing of class instancer functions can be eased through macro:
#define LINK_MODULE(Class) extern "C" _declspec(dllexport) IInstantiable* create##Class (void) \
{ \
return static_cast<IInstantiable*>(new Class ()); \
}
Basically, this is the approach I took for my PhD thesis; with the difference that I was using a dynamic list of libraries instead of just a single handle.
If for some reason you need to print a list of all available export functions in a DLL, you can use the following function which read the module DOS header:
void listModuleExports(HMODULE hModule)
{
if(((PIMAGE_DOS_HEADER)hModule)->e_magic != IMAGE_DOS_SIGNATURE)
return;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)hModule + ((PIMAGE_DOS_HEADER)hModule)->e_lfanew);
if(pNtHeader->Signature != IMAGE_NT_SIGNATURE)
return;
dword dwExportAddr = pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if(dwExportAddr == 0)
return;
PIMAGE_EXPORT_DIRECTORY pExportDesc = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)hModule + dwExportAddr);
if(pExportDesc->AddressOfNames == 0)
return;
dword *pNames = (dword*)((PBYTE)hModule + pExportDesc->AddressOfNames);
for(dword dwI=0;dwI<pExportDesc->NumberOfNames;dwI++)
{
char *szName = (char*)((PBYTE)hModule + (dword)pNames[dwI]);
printf("%s\r\n", szName);
}
}
A good thing about the export function approach is that it is not only working for DLL but also for the EXE file itself as, in Windows at least, it behaves exactly the same as a DLL. So just use the current module handle through the GetModuleHandle function.
The export function approach has a lot of advantages but we are mainly relying on the linker mechanism to generate a list of entry point from strings. But we can also mimic this behaviour by using classical object-oriented programming design too.
The idea is to use a global object (the instancer) which stores a list of class name and entry points for function that creates instances of them (I have chosen to implement it as a singleton):
class Instancer
{
public:
typedef Instantiable* (*pfnInstancer)(void);
TypeRet createInstance(const char *szName) const
{
if(szName == null)
return null;
AutoPtr< Iterator<struct instancer_s> > it = this->m_cList.createForwardIterator();
while(!it->end())
{
struct instancer_s& curr = it->current();
it->next();
if(strEquals(curr.szName, szName))
return (*curr.pfnCallback)();
}
return null;
}
static Instancer *pInstancer;
static Instancer *getInstance(void)
{
if(pInstancer == null)
pInstancer = new Instancer();
return pInstancer;
}
private:
Instancer(void) {}
void add(char *szName, pfnInstancer pfnCallback)
{
struct instancer_s s;
s.szName = szName;
s.pfnCallback = pfnCallback;
this->m_cList.add(s);
}
struct instancer_s
{
char *szName;
pfnInstancer pfnCallback;
};
LinkedList<struct instancer_s> m_cList;
};
Instancer *Instancer :: pInstancer = null;
But so far we still have to populate the list ourselves which is not what we want. So the next step is to use the RIIA mechanism to create an object whose role is to add the class name and entry point in the list:
class Instancer
{
public:
class AutoRegister
{
public:
AutoRegister(char *szName, pfnInstancer pfnCallback)
{
Instancer *pInstancer = Instancer :: getInstance();
if(pInstancer != null)
pInstancer->add(szName, pfnCallback);
}
};
...
};
#define LINK_MODULE(Class) IInstantiable *createInstance__##Class(void) \
{ \
return new Class (); \
} \
\
Instancer :: AutoRegister g_cRegister##Class(#Class, createInstance__##Class);
This is basically the mechanism I have been using for a few years in my game engine except that I have tuned it using templates to allow different instancers for different kind of instance interfaces. So you can see it as a bit more flexible than the export function approach. Also, we are using 100% C++ and so the design is conceptually better.
Finally, you can also create one instancer per DLL (if you are using them) and call an export function to create instances from each DLL. But that is another story...
You may also like:
[»] Event Monitoring in C++ Using EVEMON.EXE
[»] Understanding Least-Squares Fitting
[»] Power Functions with Op-Amps