c++ code tutorial windows

A Brief Overview of DLLs (dynamic-link libraries)

In this article, I will explain what a DLL is, and a basic example of how to write one in C++.

What is a dll?

There are two types of Library; a static and a shared library. Microsoft’s Shared library is a DLL, or dynamic link library. The idea behind a library is to allow for centralized access to a subset of functions. For example, a game might have a sound engine written that other games by the same author can use. Rather than keep reimporting the code (which would be messy and lead to issues keeping up with the same version between all games), a library can be employed. The programmer can either create a static or shared library, which would contain all of the code for handling sounds. From that point, each game can link to the library that was generated and reuse the code. This has the affect of making programs more modular, where any developer can include a subset of functions, which are used to perform a set of tasks.

Static libraries

Static libraries are statically linked at compile-time. This means that the required functions and code are included within the generated executable, and thus this is not a shared resource. This is useful for example when you have split your code into modules (or are employing other libraries).

While programs built using static-linked libraries are easier to distribute, as they are built directly into the generated executable, the downside is two-fold. The footprint of the executable is larger. This directly translates into higher disk usage (if you have two executables using the same library), and higher memory usage as well.

Shared libraries

A shared library is useful in many scenarios. First, a set of shared libraries can be distributed when a user will have one or more products installed. This not only decreases the deployment size, but also means that as long as the interface does not change, you could minimize the size of updates by deploying individual libraries and executables.

Whereas static libraries are loaded into the same memory as your executable, shared libraries work differently; they are loaded into shared memory, and individual processes that use them have access to that shared memory.

The modular aspect of shared libraries means that they can be loaded as plugins, which can be used to easily extend applications. A developer of an application can provide an interface through which the application can be controlled by an external shared library. For example, if you are building a media player, you might want to enable plugins that change the appearance, handle different file types, etc. By providing a common set of interfaces for the plugins and the application itself, an application can load and unload specific plugins at runtime.

How does it work?

When a dll is loaded, either dynamically through a call to LoadLibrary, or dynamically at startup, it’s code segment is mapped into the address space of the calling process, while the data segments, (unless explicitly marked as shared) remain isolated per program. This means that each program uses the same code segment as all other programs, but the data segment remains separate. This is one of the benefits of shared libraries; if every program were linked statically, it’s disk and memory usage footprint would be considerably larger. Since pages marked executable can not be written to, the operating system can load one instance of a shared library into memory and allow all programs to access it, thus drastically decreasing the memory consumption of each process.

Implementation

Like your average program, a DLL has to start off with an entry point which is called when the library is loaded, or when a process or thread is attached or detached. The dllmain function has the following prototype:


BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);

The first argument is a handle to the dll itself. This is useful to pass to functions that require a handle to the module such as GetModuleFileName. The reason is why the dllmain function was called, and can be any of the following:

  • DLL_PROCESS_ATTACH: The dll was mapped into the address space of a process, either through a call to LoadLibrary, or because it was linked to and loaded at startup.
  • DLL_PROCESS_DETACH: The dll is being unloaded from memory because the process was terminated, FreeLibrary was called, or there was an error.
  • DLL_THREAD_ATTACH: When a new thread is created, the entry-point of every dll that has been mapped into that process is called.
  • DLL_THREAD_DETACH: A thread has exited cleanly.

The reason argument can be used to manage process and/or thread specific storage. By allocating objects per process, you could deallocate the same objects when the process is detached.

The final argument, reserved is the most tricky; it can be treated as a boolean value to help determine what happened. If the reason is DLL_PROCESS_ATTACH, then reserved is NULL if the library was loaded dynamically through a call to LoadLibrary, or not NULL if the library was loaded statically at startup. If the reason is DLL_PROCESS_DETACH, reserved will be NULL if there was a call to FreeLibrary, or if loading of the dll failed. Similarly, reserved will not be NULL if the process terminated cleanly.

Returning from dllmain with true will mean that the DLL was loaded successfully. If false is returned and there was a call to LoadLibrary, LoadLibrary will return NULL. If false is returned and the DLL was loaded at program startup, the process will terminate with an error.

Writing the application

In this tutorial, we will be writing a basic application, which will make a call to a DLL. All the dll will do is add two numbers, but it will show how other functions can be exported.

First, we have the code we need to get the numbers from a user:


#include <iostream>

using namespace std;

int main(int argc, const char** argv)
{
    cout << "Enter your first number." << endl;
    int a = 0;
    cin >> a;
    cout << "Enter your second number." << endl;
    int b = 0;
    cin >> b;
    cout << Add(a, b) << endl;
    return 0;
}

Next, we need to write the dll:

Writing the DLL

Addlib.h:


#ifndef ADDLIB_H
#define ADDLIB_H
#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#define EXPORT __declspec(dllexport)
#define IMPORT __declspec(dllimport)

IMPORT int Add(int a, int b);

#endif

main.cpp:


#include "addlib.h"

EXPORT int Add(int a, int b)
{
    return a+b;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    return true;
}

Explaination

When we compile the dll, a static library is generated. A program can link to the library and call the symbols exported. As you may have noticed, in the addlib.h file we defined export and import macros. You need to export functions from the source file, and import them in the header file. In order to access the symbols, you will need to do two things; first, you need to include the header file where your prototypes are declared. From the adder project, since I created the addlib project in the adder directory, I could just use:


#include "addlib/addlib.h"

You will also need to link to the .lib that is generated when you build your dll. Since there is a debug and release version, you will probably want to do this in properties, where you can provide the path to the library based on the build.

Using DLLs in your own project

In order to use DLLs in your own project, you will need to create the DLL. From visual studio, go to file->new->project, choose a c++ project and choose Win32 Console Application. Enter the name of the project and click OK. From the wizard, click next and choose DLL. You can either start with an empty project or let Visual Studio help you get started. From there click Finish and you will either have an empty file, or a template project that you can start working with.

Conclusion

I hope this article was useful. Please feel free to leave any comments, ideas or suggestions in the comment box below.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.