A dynamically linked library is just like a static library: it contains a set of useful functions that can be called from other software. As with normal .lib libraries, there is no main function.
Unlike static libraries however, they have several features that make them a very interesting alternative to static libraries:
· &nb 818c27i sp; When they are loaded, the loader calls a function of the library to allow load time initializations. This allows us to register our class, for instance, or do other things.
· &nb 818c27i sp; When the program that loads them starts or ends a new thread the system arranges for calling the same function. This allows us to take special actions when this event occurs. We do not need this feature for our application here, but other software do.
· &nb 818c27i sp; When the library is unloaded, either because the program explicitly does it or because simply the program is finished, we get notified. Here we can reverse the actions we performed when the library was loaded: we can, for instance, unregister our window class.
· &nb 818c27i sp; DLLs can contain resources. This solves the problem of forcing the user of the library to link a bunch of resources to his/her program.
DLLs need to specify which functions are going to be exported, i.e. made visible to the outside world. With static libraries this is not really necessary since the librarian will write all the symbols with the external type to the library symbol table automatically.
We can declare that a symbol will be exported using two methods:
1. &nb 818c27i sp; We can put in the declaration of the symbol the __declspec(dllexport) mark.
2. &nb 818c27i sp; We can write the name of the symbol in a special file called definitions file (with the .def extension) and pass this file to the linker.
Which method you use is a matter of taste. Writing __declspec(dllexport) in the source code is quite ugly, and may be non-portable to other systems where the conventions for dynamically linked code may be completely different. A definitions file spares us to hardwire that syntax in the source code.
The definitions file has its drawbacks too however. We need yet another file to maintain, another small thing that can go wrong.
For our example we will use the __declspec(dllexport) syntax since we have only one function to export.
We return to our library project, and reopen it. We go again to the linker configuration tab, that now is called "librarian" since we are building a static library, and we check the radio-button corresponding to a DLL project. We answer yes when Wedit says whether it should rebuild the makefile and there we are. Now we have to make the modifications to our small library.
We have to define a function that will be called when the library is loaded. Traditionally, the name of this function has been LibMain since the days of Windows 3.0 or even earlier. We stick to it and define the following function:
int WINAPI LibMain(HINSTANCE hDLLInst, DWORD Reason, LPVOID Reserved)
return TRUE;
This function, like our dialog function or many other functions under windows, is a callback function, i.e. a function that is called by the operating system, not directly from our code. Because of this fact, its interface, the arguments it receives, and the result it returns is fixed. The operating system will always pass the predefined arguments to it, and expect a well-defined result.
The arguments that we receive are the following:
1. &nb 818c27i sp; We receive a HANDLE to the instance of the DLL. Note that we have to pass to several functions this handle later on, so we will store it away in a global variable.
2. &nb 818c27i sp; We receive a DWORD (an unsigned long) that contains a numerical code telling us the reason why we are being called. Each code means a different situation in the life cycle of the DLL. We have a code telling us that the we were just loaded (DLL_PROCESS_ATTACH), another to inform us that we are going to be unloaded (DLL_PROCESS_DETACH), another to inform us that a new thread of execution has been started (DLL_THREAD_ATTACH) and another to tell us that a thread has finished (DLL_THREAD_DETACH).
3. &nb 818c27i sp; The third argument is reserved by the system for future use. It is always zero.
The result of LibMain should be either TRUE, the DLL has been correctly initialized and loading of the program can continue, or zero meaning a fatal error happened, and the DLL is unable to load itself.
Note that we return always TRUE, even if our registration failed.
Why?
If our registration failed, this module will not work. The rest of the software could go on running however, and it would be too drastic to stop the functioning of the whole software because of a small failure in a routine that could be maybe optional.
Why the registration of our class could fail?
One of the obvious reasons is that the class is already registered, i.e. that our calling program has already loaded the DLL, and it is loading it again. Since we do not unregister our class, this would provoke that we try to register the class a second time.
For the time being, we need to handle only the event when the DLL is loaded or unloaded. We do two things when we receive the DLL_PROCESS_ATACH message: we store away in our global variable the instance of the DLL, and then we register our string dialog box class. We could have just done it in the LibMain function, but is clearer to put that code in its own routine. We write then:
static void DoRegisterClass(void)
You see that the code is the same as the code we had originally in WinMain, then in our GetString procedure, etc.
To finish LibMain, we handle the message DLL_PROCESS_DETACH unregistering the class we registered before.
With this, our GetString procedure is simplified: We do not need to test our flag to see if the class has been registered any more. We can be certain that it was.
int APIENTRY __declspec(dllexport)
GetString(char *prompt, char *destbuffer,int bufferlen)
return result;
We compile and link our DLL by pressing F9. Note that when we are building a DLL, lcc-win32 will generate three files and not only one as with a normal executable.
1. &nb 818c27i sp; We obtain of course a dialog.dll file that contains the DLL.
2. &nb 818c27i sp; We obtain an import library that allows other programs to be linked with this DLL. The name will be dialog.lib, but this is not a normal library. It is just a library with almost no code containing stubs that indicate the program loader that a specific DLL is used by the program.
3. &nb 818c27i sp; We obtain a text file called dialog.exp that contains in text form the names that are exported from the DLL. If, for any reason we wanted to regenerate the import library for the DLL, we could use this file together with the buildlib utility of lcc-win32 to recreate the import library. This can be important if you want to modify the names of the exported functions, establish synonyms for some functions or other advanced stuff.
|