Loading DLLs using C++ in Windows
This standalone tutorial will guide you through writing your own DLL library from scratch and loading it into C++ and calling the exported functions in a separate project.
Hello, friends 😀! Today I will guide you through libraries and how you have implemented other's libraries in your C++ code in the Windows operating system. Hold your pace, we will walk together and explore libraries. But before moving forward, let me give short details on what this post is all about.
What are Libraries?
Like you use the information from the books kept in the library (the real one). In programming, you can use the knowledge (functions/routines) of implementation from some other author or community. Reusing others' code to avoid time in the re-implementation is really cool thing. It let you focus on the logic for your application rather than the utility
There are two parts for variables and functions: prototyping and definition.
In the case of prototyping, you basically create the signature of the identifier and register its name, return type and parameters (When you pass the value, it's called arguments and while accepting in functions they are called parameters)
Defining is when you actually implement the code for the function or variable (aka initializing). In this phase, you must use the same syntax of the identifier that you have chosen while prototyping.
Some veteran C++ developers, also sometimes called prototypes as an identifier signature.
So, the library contains definitions of these identifiers prototyped in the respective headers file. For example, you use functions from the cmath
header file, which are defined in the libm
library. In GCC you can do this with,
Here -lm
means to include libm.so
file and provide the definitions for the functions. Well, we won't be using GCC in the Windows environment.
Since the code of the libraries rarely changes, they won't recompile every time you hit that Build button and provide a fast compilation.
Different types of Libraries
There are two types of libraries that can be used in the program.
- Static libraries
- Shared libraries
Static libraries (.lib
) contains definitions that are linked and embedded in the executable. So they make your executables portable and also bulky. If you call the executable n number of times, the library will be loaded n times along with the code in memory. Also if you have to update the code in the static library, you will require to compile the source of the dependents.
Shared libraries are also called Dynamic Link Libraries (aka DLL) also contain the same code definitions but unlike static libraries, they are not embedded in the code. It is loaded once in the memory when first called, after that, it is reused by the code. This opposes all the points that we have discussed for static libraries.
There are two methods of calling a function from DLL
- Load time linking
- Run time linking
So basically in run-time linking, you first load the library and then later call the function to get the address of exported functions and call it like a normal function. In this post, I will focus on Run-time linking. You can read about others from this article
NOTE – Though load time linking is faster than run time as it optimizes calls of GetProcAddress, but runtime dynamic linking is preferable when the startup performance of the application is important.
But where is my DLL?
Like you, I also had this question in my mind. How do programs look for the dynamic library while loading and what if they couldn't find it anywhere? When you will perform the LoadLibrary
call (I will explain it). It will search the name of the DLL file in the following order as described here.
Notice one thing, Safemode? If you do disable the DLL search safe mode, an attacker can do DLL search order hijacking and the program will load their malicious DLL, eventually taking over control of the system.
In case the dll is not found in the above order, LoadLibrary will return NULL
. So you can add safeguard.
Writing a DLL in C++
In this demonstration, I will be creating a simple arithmetic Dll that perform addition, subtraction, division and multiplication. Yes, so naive. This will not only help in learning how to load libraries but also how they are created.
DLL Entrypoint (DllMain
)
Like a normal C++ program, DLLs also have an entry point known as DllMain
It will be executed whenever a Dll file is loaded or unloaded to the running program/thread.
But unlike the main
function in your C++ code, this function is optional. If present, the system calls the entry-point function whenever a process or thread loads or unloads the DLL. In this project, I used the DllMain
function to print the text on the console, you can check it here
DLL Functions
It is now time to add our functions in the DLL source file here. You can check the content below.
It is always intended to use the libraries in both C and C++, kind of backward compatibility you can say. EXTERN_C
here is a macro that gets resolved to extern "C"
in the compile-time (source code from wine) only if the library code is compiled from a C++ compiler.
Let's now create a signature for the functions we are going to use for typecast in the C++ code where we will be using this DLL. Here in this example, I will create 4 functions to perform arithmetic operations.
This syntax in C++ is called function pointers that let you pass functions as parameters or return them as values. It is the fundamentals of what you call "lambda function" in high-level languages like python or javascript. When the application is loaded in the memory, all you need is the reference to its memory location and syntax ([variables...])
to execute that instruction set. These functions signatures will be used to typecast an address that you can call a normal function while dereferencing the pointer got from the GetProcAddress
function
Note:– Since all the functions have the same signature with two arguments of DWORD
type and return type is also DWORD
. You can actually have only one function (shown below) and call it any name. These names are only for your understanding. In the reality, it will execute the instructions from the address of the function. But for learning purposes, I will stick to the above syntax
Exporting DLL Functions
Once you have written the functions in your library source code, it needs to be exported using a module definition file that tells the linker about the functions that can be used in the applications source code. This is supposed to be done by creating a Source.def
file in the project and configuring it in the Linker Settings as shown below
There is another way to export the functions using (shown below). I prefer to use module definitions as they are a cleaner way of configuration.
Finally, everything is done for creating the DLL in the visual studio 🤩! All you need to do now is to compile. Now let's jump on to using it in the C++ code.
Using a DLL in C++ code using LoadLibraryW
function
Now is the climax where I will show you how to load the DLL and call the exported function from it. The source code is pretty simple and can be found in the Source.cpp
file
The code starts with the _tmain
function which is replaced with wmain
in the compile-time and is used for handling UNICODE strings in the application. Then I added the check for handling invalid CLI arguments passed to the application and casting the LPWCSTR
to DWORD
as shown here. So a basic template of application source code looks like below
Now you need to first load the library using the LoadLibrary
function which is defined in libloaderapi.h
header and aliased to LoadLibraryW
using #define
macros. It accepts only one argument: a long pointer to wide string as the file name of the library.
In return, it will give you a handle of the DLL module mapped into the virtual address of the current process. If this handle value is either NULL
or INVALID_HANDLE_VALUE
which you can then as safe-guard for further processing.
Once the library is loaded successfully, you can have its address using the following code
Finally, it's now time to get the reference of function in the DLL and execute it using the normal function call. The address of function can be obtained using the GetProcAddress
function from libloaderapi.h
header file which accepts two parameters: DLL handle and the name of the exported function respectively. If the function succeeds, it will return the address of the exported function.
After you have got the address all you need to this call the function and as it is defined in the current project Add(dwX, dwY)
.
Before building the project it is required to add the DLL into the reference. You can do it by selecting References in the solution explorer and selecting the MyDLL
project.
Lastly, when everything is completed and DLL is no longer required, you need to release the memory using the FreeLibrary
function from libloaderapi.h
. It accepts only one parameter: a handle to the loaded DLL module and returns TRUE
on success otherwise FALSE
You can find it implemented in the last of the main function here
References
- https://docs.microsoft.com/en-us/cpp/build/dlls-in-visual-cpp?view=msvc-170
- https://github.com/wine-mirror/wine/blob/master/include/winnt.h#L442
- https://docs.microsoft.com/en-us/windows/win32/dlls/about-dynamic-link-libraries#types-of-dynamic-linking
- https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#standard-search-order-for-desktop-applications
- https://www.contextis.com/en/blog/dll-search-order-hijacking
- https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain
- https://stackoverflow.com/questions/895827/what-is-the-difference-between-tmain-and-main-in-c
- https://www.pinvoke.net/default.aspx/Constants/INVALID_HANDLE_VALUE.html
- https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain
- https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress
- https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw
- https://docs.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-def-files?view=msvc-160
- https://docs.microsoft.com/en-us/cpp/cpp/extern-cpp?view=msvc-160
- https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-freelibrary