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.

Loading DLLs using C++ in Windows
Photo by Iñaki del Olmo / Unsplash

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)

void greet(std::string name);
Prototyping a function named greet

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.

void greet(std::string name) {
	std::cout << "Hello, " << name << std::endl;
}
Implementing instructions to be performed when greet function is called with name as its argument

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,

gcc arithmatic.c -lm -o arithmatic
Compiling a program with a library libm.so

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.

HMODULE hLib = LoadLibrary(L"MyLibrary");

if (hLib == NULL) {
	std::wcerr << "Library MyLibrary.dll not found" << std::endl;
	exit(1);
}
Checking whether LoadLibraryA found the target library or not

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.

BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // handle to DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpReserved )  // reserved
{
    // Perform actions based on the reason for calling.
    switch( fdwReason ) 
    { 
        case DLL_PROCESS_ATTACH:
         // Initialize once for each new process.
            break;

        case DLL_THREAD_ATTACH:
         // Do thread-specific initialization.
            break;

        case DLL_THREAD_DETACH:
         // Do thread-specific cleanup.
            break;

        case DLL_PROCESS_DETACH:
         // Perform any necessary cleanup.
            break;
    }
    
    // Successful. If this is FALSE, the process will be terminated eventually
    // https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-entry-point-function#entry-point-function-return-value
    return TRUE;  
}
DllMain is the entry function when a particular DLL is loaded into the process's memory

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.

EXTERN_C DWORD AddFunc(DWORD x, DWORD y) {
    return x + y;
}

EXTERN_C DWORD SubFunc(DWORD x, DWORD y) {
    return x - y;
}

EXTERN_C DWORD DivFunc(DWORD x, DWORD y) {
    return x / y;
}

EXTERN_C DWORD ProFunc(DWORD x, DWORD y) {
    return x * y;
}
Writing the definitions of the functions in the DllMain and exporting it

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.

#pragma once
#include <Windows.h>

typedef DWORD(*AddFunc)(DWORD, DWORD);
typedef DWORD(*DivFunc)(DWORD, DWORD);
typedef DWORD(*SubFunc)(DWORD, DWORD);
typedef DWORD(*ProFunc)(DWORD, DWORD);
Signature of functions that will be exported by DLL

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

#pragma once
#include <Windows.h>

typedef DWORD(*Arithmetic)(DWORD, DWORD);
A simplified version of the exported function signatures

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

Specifying the .def file in the Linking option under project properties

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.

__declspec(dllexport) void Function1(void);
Telling the linker that the library is exporting Function1

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

#include <Windows.h>
#include <tchar.h>
#include <libloaderapi.h>
#include <wchar.h>
#include "../MyDLL/Header.h"

int _tmain(DWORD argc, LPTSTR* argv) {
	if (argc < 4) {
		_tprintf(_T("usage: %ws <x> <y> <add|div|prod|sub>\n"), argv[0]);
		return 1;
	}

	// convert TCHAR* to DWORD
	DWORD dwX = _wtoi(argv[1]);
	DWORD dwY = _wtoi(argv[2]);
    
    // add more code here
}
Template for the C++ arithmetic calculation code

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.

HMODULE LoadLibraryW(
  [in] LPCWSTR lpLibFileName
);
Signature of LoadLibraryW function from libloaderapi.h header file

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.

HMODULE hDll = LoadLibrary(_T("MyDLL"));
if (!hDll || hDll == INVALID_HANDLE_VALUE) {
	_tprintf(_T("unable to load library"));
	return 1;
}
Load and validate the DLL library from the search order

Once the library is loaded successfully, you can have its address using the following code

_tprintf(_T("library loaded at 0x%x\n"), hDll);
Printing the address of the Dll handle

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.

FARPROC GetProcAddress(
  [in] HMODULE hModule,
  [in] LPCSTR  lpProcName
);
Signature of GetProcAddress function from libloaderapi.h header file

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).

AddFunc Add = (AddFunc)GetProcAddress(hDll, "AddFunc");
if (!Add) _tprintf(_T("unable to load AddFunc\n"));
else _tprintf(_T("%d + %d = %d\n"), dwX, dwY, Add(dwX, dwY));
Loading AddFunc, casting to the appropriate type and calling with function with the parameters

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.

Referencing the library in the DLLLoad 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

BOOL FreeLibrary(
  [in] HMODULE hLibModule
);
Signature of FreeLibrary function from libloaderapi.h header file

You can find it implemented in the last of the main function here

FreeLibrary(hDll);
Free the loaded DLL when not required

References