Introducing Dynamic Link Libraries

What is a Dynamic Link Library?

A dynamic link library (or shared library) takes the idea of an ordinary library (also called a statically linked library) one step further. The idea with a static library is for a set of functions to be collected together so that a number of different programs could use them. This means that the programmers only have to write code to do a particular task once, and then, if they are good programmers, they can use the same function in lots of other programs that do similar things. However, with static linking the linker builds a program from all the object files that contain functions or data used in the program, which includes any library functions that are used. Each program gets it's own copy of all the library functions built into it.

A natural extension of the idea of a library is to put all the code for the functions in a library, but do the linking when the program is run instead of at link time (thus it is "dynamic"). A dynamic link library is a lot like a program, but instead of being run by the user to do one thing it has a lot of functions "exported" so that other programs can call them. There are several advantages to this. First, since there is only (in theory) one copy of the DLL on any computer used by all the applications that need that library code, each application can be smaller and save disk space. Also, if there is a bug in the DLL a new DLL can be created and the bug will be fixed in all the programs that use the DLL just by replacing the old DLL file. DLLs can also be loaded dynamically by the program itself, allowing the program to install extra functions without being recompiled.

How Does It Work?

Consider two files: a program foo.exe, and a dynamic link library bar.dll which foo.exe uses. How does the operating system connect these two files when foo is run? What happens is this: first the program file foo.exe is loaded, and the operating system finds a table of data in the file which states, effectively, "this program uses the following list of functions from the DLL bar.dll". This is called a list of imports or imported functions from the DLL bar.dll in the program foo.exe. The loader code next searches for the file bar.dll, and if it finds it then the file is loaded. Inside the bar.dll file is another list. This list, called the export list, gives the address inside the DLL file of each of the functions which the DLL allows other programs to access. Then the loader goes through both lists and fills in a table inside foo.exe (actually the copy of that table in memory) with the addresses of all those functions in bar.dll (based on where the dll was loaded in memory by the OS). Now, whenever the program wants to call a function in the DLL it simply uses the address stored in the appropriate table entry by the loader.

What this means is that a few extra things have to be done to use DLLs as compared to ordinary libraries. Each program must contain an import list for each DLL it uses, which I will talk about in the page on using DLLs, and each DLL must contain an export list, which I will talk about in the page on creating DLLs.

Relocatable Dynamic Link Libraries

In the page on creating DLLs I will show how to make a "relocatable" DLL. It is possible, and simpler, to make non-relocatable DLLs, but it is a very bad idea most of the time.

When Windows loads your program it creates a whole new "address space" for the program. What this means is when your program contains the instruction "read memory from address 10A0F0" or something like that the computer hardware actually looks up in a table to figure out where in physical memory that location is, and performs the read operation on that memory. This is all done in hardware so it's very fast, and it means that every program can behave like it is running on a whole separate computer. The address 10A0F0 in another program would mean a completely different part of the physical memory of the computer.

Now, programs, when they are loaded, are "mapped" into address space. This process basically copies the code and static data of your program from the executable file into a certain part of address space, for example, a block of space starting at address 40000. The same thing happens when you load a DLL. Maybe you see what the problem could be?

A DLL, or a program for that matter, tells the operating system what address it would prefer to be mapped into. But, although the same address means different things to different programs, within a single program an address can only be used once. So what if there are two DLLs that both want to be mapped to address 100000? The OS will map the first one loaded to that address no problem, but the next one cannot be put there.

So the OS checks to see if the second DLL is relocatable. If so, then it maps the DLL to a different part of memory, say 200000. Then it performs the necessary relocations.

Why can't you just map the DLL to a separate part of memory? Well, the problem is that inside the DLL there are bits of code that say things like "read data from 10A0F0" or "call function at 101000". These addresses assume that the DLL is mapped into the part of address space that it prefers. If the DLL is mapped into a different part of memory then a call to 101000 might end up in the middle of the code of a different DLL, or in data, or nowhere at all.

So a relocatable DLL contains information so that the OS can change all those internal addresses. So that if the DLL wanted to be loaded at 100000, but was relocated to 200000 then the read data code will read from 20A0F0 instead of 10A0F0, and the function call will go to 202000 instead of 101000.

If you don't include this special data in your DLL it will be non-relocatable, and that means that if another DLL gets loaded into the preferred address first the program will not be able to load your DLL, and will probably not run at all.