Dynamic Linking: Windows and Unix
Dynamic Linking is the first feature OS’s adopted. It’s history can be traced all the way back to the GM_NAA I/O Operating System. You may not have heard of this OS, and by modern standards it wasn’t an OS. No time sharing, no multi-processes, no process re-ordering, no file system. But what did it have? Dynamic Linking!
The dynamic linking allows for Batch Jobs (programs ran one after another) to share memory, and functionality. This was a big breakthrough! You didn’t have to implement a line-printer driver in every program! Imagine your free time! The gained productivity!
This leads me to a theory. Writing code has, and always will suck. When you get a piece of code that by some weird twist of fate actually does what you want it to do. You dig in your claws and never let it go. This is true in 1956, and is also true today.
So what do we use dynamic loading for today?
- Standardize program interactions. If both programs use the same library+function, they’re running the same code. They should behave the same.
- Decrease binary size. If your executable is using a 4KB library and you statically link to the library, your executable just got 4KB larger. While with dynamic loading you can sort this out at run time. Saving Disk and RAM space.
- Code re-use. Don’t re-write or re-compile what you don’t need to.
So lets dive into the nitty gritty how one dynamic link to a library?
First you need to get a OS Handle to the DLL. This is accomplished by GetModuleHandleEx(3), GetModuleHandle(1), LoadLibraryEx(3), or LoadLibrary(1). You want to use GetModuleHandle(1) if the library is already loaded (or a default system library), and LoadLibrary(1) if the library isn’t already load. The Ex functions offer options about how and what happens as the library is loaded.
Now you’ll take your OS handle and pass it to GetProcAddress(3) which will return a function pointer you can cast to a function, and call.
When you are done with a library one needs to call FreeLibrary(1) or FreeLibraryAndExitThread(2). This is because DLL’s are reference counted allocations and loading/unloading them triggers increasing/decreasing that reference count.
FreeBSD/OSX/Linux all attempt to be POSIX systems. So they all use dlfcn.h for their dynamic linking and roughly use the same interface. I’m going to outline the general process, then discuss a few differences.
To get a handle to a dynamic library one calls dlopen(2). Then to get a function pointer you give the handle to dlsym(2) which gives you a pointer you can cast to a function and call. When done with a dynamic library handle one calls dlclose(1) with the handle they received from dlopen(2).
Linux just does the above things, nothing special.
FreeBSD has all the same interfaces but also offers fdlopen(2) which allows you to open a dynamic library from a file descriptor if you opened the dynamic library with open(2) instead of dlopen(2). It also offers dlfunc(2) which is literally identical to dlsym(2) except in the fact that it is more compiler friendly this is because casting a data pointer to a function pointer is technically undefined behavior.
OSX is identical to Linux except when one calls malloc(3) from within a dynamically loaded function the allocated memory DOES NOT NECESSARILY belong to that process you have to take ownership of it with malloc_zone_from_ptr(1). The line here is fuzzy and has to do with how much free memory the OSX process has allocated from the kernel vs how much is being requested in the malloc(3) call. This behavior can also change with some custom allocator libraries.