Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
38 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

  Contents

 Introduction
 LoadTime Linking
 RunTime Linking

 Printable version

 


RunTime Linking

When using RunTime linking, the client doesn't rely on the OS but loads the DLL explicitly in the code when it wants to. Furthermore it doesnt need to link to any .lib files so you don't have to recompile the client just to link to a newer lib file. RunTime linking is powerful in the sense that YOU the programmer have the power to decide which DLL to load. Lets say you are writing a game which supports DirectX and OpenGL. Either you can just include all the code in the executable which might be hard to maintain if more than one person is working on it. Or you can seperate the DirectX code in one DLL and the OpenGL code in another and link to them statically so that they are loaded at LoadTime. But now all the code depends on eachother, and if you have a new DirectX DLL then you have to rebuild the executable as well. Apart from that, both DLLs will be automatically loaded when the program starts thus increasing your memory usage. The last, and in my opinion the best, idea is to let the executable decide which DLL to load once it starts. If you find that the system doesn't have OpenGL acceleration then you would load the DirectX DLL, otherwise you would load the OpenGL DLL. Thus RunTime linking reduces memory usage and dependencies. However the limitation here is only C style functions can be exported. Classes or variables cannot be loaded from a DLL when you load it during Runtime. But I'll show how to get around that by using Interfaces.

DLLs designed for RunTime linking usually use .def files to define the functions they will export. If you don't want to use .def files then you can just prefix the C functions to be exported with the __declspec(dllexport) keyword. Both of these methods accomplish the same thing. The client loads a DLL by passing the name of the DLL to the Win32 LoadLibrary() function. This returns a HINSTANCE handle which you should keep track of, as its needed when you want to unload the DLL. Once it has loaded the DLL, the client can get a pointer to any of the exported functions by calling GetProcAddress() with the function name.

//============================================================ //Dlls .def file LIBRARY myfirstdll.dll DESCRIPTION 'My first DLL' EXPORTS MyFunction //============================================================ /* Dlls implementation of the function */ bool MyFunction(int parms) { //do stuff ............ } //============================================================ //Client code /* The function declaration really isn't required but the client needs to be of the function parameters. These are usually supplied in a header file accompanying the DLL. The extern C with the function decleration tells the compiler to use C-style linkage. */ extern "C" bool MyFunction(int parms); typedef bool (*MYFUNCTION)(int parms); MYFUNCTION pfnMyFunc=0; //pointer to MyFunction HINSTANCE hMyDll = ::LoadLibrary("myfirstdll.dll"); if(hMyDll != NULL) { //Get the functions address pfnMyFunc= (MYFUNCTION)::GetProcAddress(hMyDll, "MyFunction"); //Release DLL if we werent able to get the function if(pfnMyFunc== 0) { ::FreeLibrary(hMyDll); return; } //Call the Function bool result = pfnMyFunc(parms); //Release the DLL if we dont have any use for it now ::FreeLibrary(hMyDll); }

As you can see the code is very straight forward. Lets see how to get "classes" working by building on the same concept. As mentioned earlier, there is no way to directly import classes from a DLL when using RunTime loading, so what we can do is to expose the "functionality" of a class by defining an interface which contains all of its public functions apart from the constructor and destructor. The interface will be a typical C/C++ structure containing only pure virtual functions. The actual class in the DLL will inherit from this struct and it will implement the functions defined in it. Now to allow access to this class from the client, all we need to do is to export a standard C-style function that instantiates the class, casts it to the interface so that the client can use it, and returns it. Another function should be exported that deletes the interface once the client is done with it. A sample implementation is given below.

//============================================================ //Dlls .def file LIBRARY myinterface.dll DESCRIPTION 'provides interface to I_MyInterface EXPORTS GetMyInterface FreeMyInterface //============================================================ //Shared header between Dll and Client which defines the interface //I_MyInterface.h struct I_MyInterface { virtual bool Init(int parms)=0; virtual bool Release()=0; virtual void DoStuff() =0; }; /* Declarations for the Dlls exported functions and typedef'ed function pointers to make it easier to load them. Note the extern "C" which tell the compiler to use C-style linkage for these functions */ extern "C" { HRESULT GetMyInterface(I_MyInterface ** pInterface); typedef HRESULT (*GETINTERFACE)(I_MyInterface ** pInterface); HRESULT FreeMyInterface(I_MyInterface ** pInterface); typedef HRESULT (*FREEINTERFACE)(I_MyInterface ** pInterface); } //============================================================ //Dlls implementation of the interface // MyInterface.h class CMyClass: public I_MyInterface { public: bool Init(int parms); bool Release(); void DoStuff(); CMyClass(); ~CMyClass(); //any other member funcs ............ private: //any member vars ............ }; //============================================================ //Dlls exported functions which create and delete the interface //Dllmain.h HRESULT GetMyInterface(I_MyInterface ** pInterface) { if(!*pInterface) { *pInterface= new CMyClass; return S_OK; } return E_FAIL; } HRESULT FreeMyInterface(I_MyInterface ** pInterface) { if(!*pInterface) return E_FAIL; delete *pInterface; *pInterface= 0; return S_OK; } //============================================================ //Client code //How to load the interface and call functions GETINTERFACE pfnInterface=0; //pointer to GetMyInterface function I_MyInterface * pInterface =0; //pointer to MyInterface struct HINSTANCE hMyDll = ::LoadLibrary("myinterface.dll"); if(hMyDll != NULL) { //Get the functions address pfnInterface= (GETINTERFACE)::GetProcAddress(hMyDll,"GetMyInterface"); //Release Dll if we werent able to get the function if(pfnInterface == 0) { ::FreeLibrary(hMyDll); return; } //Call the Function HRESULT hr = pfnInterface(&pInterface); //Release if it didnt work if(FAILED(hr)) { ::FreeLibrary(hMyDll); return; } //Interface was loaded, we can now call functions pInterface->Init(1); pInterface->DoStuff(); pInterface->Release(); //How to release the interface FREEINTERFACE pfnFree = (FREEINTERFACE )::GetProcAddress(hMyDll,"FreeMyInterface"); if(pfnFree != 0) pfnFree(&hMyDll); //Release the DLL if we dont have any use for it now ::FreeLibrary(hMyDll); }

Ths information should be enough to get you really comfortable when using DLLs. Feel free to contact me if you have any questions, corrections or improvements. Happy coding.

Gaz Iqbal