Building Windows DLLs with MinGW


Introduction

One question I often get asked is how to create Windows DLLs using MinGW. Under MSVC it’s pretty simple, as Visual Studio will create a barebones project with everything you need. With MinGW a bit of manual work is needed, but it’s not that difficult when you know how it’s done.

This article will show you the basics of creating DLLs with MinGW, and then linking to them in your applications. The code we produce here will be usable in MSVC as well, should you or your end users want to target multiple compilers.

DLL Basics

A DLL is a type of shared library used on Microsoft Windows and OS/2, containing functions which can be reused in any application which wants to make use of them (actually, DLLs can contain a lot more than just functions, but this article is about creating libraries of functions). If you have a library foo.dll which contains a function DoWork(), the DLL must export that function in order for it to be used by applications. Likewise, if an application bar.exe wants to make use of the function DoWork(), it must import that function from foo.dll (it is also possible to write code which loads a DLL once an application has started running, calls functions from that DLL, and then unloads the DLL when it’s no longer useful—this is how plugin / addon systems usually work). The exporting and importing of functions is fairly straightforward, and just involves a little bit of code accompanied by the correct linker command line.

The Inflexible and Inelegant Way

First we will create a DLL exporting only a very basic function which adds two integers and returns the result. Notice the “__declspec(dllexport)” attribute in the code below—this is the key to exporting functions from the DLL, and every function you would like exported from the DLL should be marked with this (any internal functions which don’t make up part of your API should be left as they are). Also notice the “__cdecl” before the function name, which declares the calling convention for the function (see the Wikipedia article on x86 calling conventions if you’re not familiar with them). The default calling convention for C functions in MinGW is cdecl, but it’s a good idea to always explicitly state the calling convention in case some other compiler has a different default—if this happens, your application will crash when calling one of these functions. Note that this first example is deliberately inflexible and inelegant in order to show exactly what’s going on behind the scenes. In the next section we will be hiding these details using preprocessor definitions.


/* add_basic.c

   Demonstrates creating a DLL with an exported function, the inflexible way.
*/

__declspec(dllexport) int __cdecl Add(int a, int b)
{
  return (a + b);
}

To compile this, it’s just necessary to pass the option “-shared” to the linker command line:

z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o add_basic.o add_basic.c

z:\Users\mpayne\Documents\MinGWDLL>gcc -o add_basic.dll -s -shared add_basic.o

No errors should be reported, and you should now have a usable DLL. I’ve separated the compiler and link step in this example, although for such a small project it could have been done simply with “gcc -o add_basic.dll -s -shared add_basic.c”. Also note the “-s” switch is used to strip debug information from the DLL—you’ll probably want to do this only for release builds.

You can now build an application which uses this DLL. All that is needed in order to use the function Add(a, b) is to declare the function with the “__declspec(dllimport)” attribute before using it (note that it’s dllimport for the client application, as opposed to the dllexport used in our DLL).


/* addtest_basic.c

   Demonstrates using the function imported from the DLL, the inelegant way.
*/

#include <stdlib.h>
#include <stdio.h>

/* Declare imported function so that we can actually use it. */
__declspec(dllimport) int __cdecl Add(int a, int b);

int main(int argc, char** argv)
{
  printf("%d\n", Add(6, 23));

  return EXIT_SUCCESS;
}

You can now compile and link this application, simply by referencing our DLL on the linker command line.

z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o addtest_basic.o addtest_basic.c

z:\Users\mpayne\Documents\MinGWDLL>gcc -o addtest_basic.exe -s addtest_basic.o -L. -ladd_basic

z:\Users\mpayne\Documents\MinGWDLL>addtest_basic.exe
29

The “-L” option specifies an additional folder that the linker should search for the DLL. In this case we want the linker to search the current directory, so we can just use a period, but for a real DLL which we have deployed to a system we would use the full path to its directory. The “-l” option specifies a DLL we want to import functions from—this should be the DLL filename without the file extension. Again, for such a small project the application could have been compiled and linked by doing a “gcc -o addtest_basic.exe -s addtest_basic.c -L. -ladd_basic

The Flexible and Elegant Way

The above demonstrates very well what’s going on, but it’s less than ideal—in a real application you wouldn’t want to declare every single imported function in every single source code file where it’s used. Instead you would place the declarations in a header file and #include the header where needed. The only problem with this is that the client application requires the functions be declared “__declspec(dllimport)”, whereas when building the DLL you must declare them “__declspec(dllexport)”. Although you could use two separate headers, this can be a bit of a maintenance headache, so instead we use some preprocessor definitions.


/* add.h

   Declares the functions to be imported by our application, and exported by our
   DLL, in a flexible and elegant way.
*/

/* You should define ADD_EXPORTS *only* when building the DLL. */
#ifdef ADD_EXPORTS
  #define ADDAPI __declspec(dllexport)
#else
  #define ADDAPI __declspec(dllimport)
#endif

/* Define calling convention in one place, for convenience. */
#define ADDCALL __cdecl

/* Make sure functions are exported with C linkage under C++ compilers. */
#ifdef __cplusplus
extern "C"
{
#endif

/* Declare our Add function using the above definitions. */
ADDAPI int ADDCALL Add(int a, int b);

#ifdef __cplusplus
} // __cplusplus defined.
#endif

Notice that we have defined “ADDAPI” as “__declspec(dllexport)” if “ADD_EXPORTS” is defined, or “__declspec(dllimport)” otherwise. This is what allows us to use the same header for the applications and the DLL. Also notice we have defined “ADDCALL” as “__cdecl”, which allows easy redefinition of the calling convention we are using for our API. Now, anywhere in our code where we need to export a function from our DLL, we specify “ADDAPI” instead of a “__declspec(dllexport)” attribute, and “ADDCALL” instead of specifying, i.e. “__cdecl”. It’s conventional to name these preprocessor definitions “[library name]_EXPORTS”, “[library name]API”, and “[library name]CALL”, so it’s a good idea to stick to this so your code is readable by yourself and others.

Finally, note that the imported / exported functions should all be wrapped in an “#ifdef __cplusplus” block containing an “extern "C"” statement. This allows the header to be used by both C and C++ applications—without it a C++ compiler will use C++ function name mangling, which is not what we want.

Bear in mind that this code isn’t portable, because it’s using Microsoft specific attributes in the code. That’s ok here because this is a tutorial about building Windows DLLs, but if you need cross‑platform compatibility then ADDAPI and ADDCALL can be defined conditionally. Typically it would be done like this:


/* add.h

   Declares the functions to be imported by our application, and exported by our
   DLL, in a flexible and elegant way.
*/

#ifdef _WIN32

  /* You should define ADD_EXPORTS *only* when building the DLL. */
  #ifdef ADD_EXPORTS
    #define ADDAPI __declspec(dllexport)
  #else
    #define ADDAPI __declspec(dllimport)
  #endif

  /* Define calling convention in one place, for convenience. */
  #define ADDCALL __cdecl

#else /* _WIN32 not defined. */

  /* Define with no value on non-Windows OSes. */
  #define ADDAPI
  #define ADDCALL

#endif

/* Make sure functions are exported with C linkage under C++ compilers. */
#ifdef __cplusplus
extern "C"
{
#endif

/* Declare our Add function using the above definitions. */
ADDAPI int ADDCALL Add(int a, int b);

#ifdef __cplusplus
} // __cplusplus defined.
#endif

The code for the DLL now includes the header we created, so all we need to specify is the calling convention for the function using our “ADDCALL” definition.


/* add.c

   Demonstrates creating a DLL with an exported function in a flexible and
   elegant way.
*/

#include "add.h"

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

To compile this DLL, it’s necessary to define “ADD_EXPORTS” when compiling the object code, to ensure “ADDAPI” is correctly defined in the header. This is done most easily by passing a “-D ADD_EXPORTS” on the command line (again, for such a simple project the whole thing could be done in one step with a “gcc -o add.dll add.c -D ADD_EXPORTS -s -shared”.

z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o add.o add.c -D ADD_EXPORTS

z:\Users\mpayne\Documents\MinGWDLL>gcc -o add.dll add.o -s -shared

The client application code is much the same as before, except that we now #include the header file we created rather than declare the function in the source file.


/* addtest.c

   Demonstrates using the function imported from the DLL, in a flexible and
   elegant way.
*/

#include <stdlib.h>
#include <stdio.h>

/* Don't forget to change this to #include <add.h> for real applications where
   the header has been deployed to a standard include folder!
*/
#include "add.h"

int main(int argc, char** argv)
{
  printf("%d\n", Add(6, 23));

  return EXIT_SUCCESS;
}

To compile the application, no change is needed to the command line we use:

z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o addtest.o addtest.c

z:\Users\mpayne\Documents\MinGWDLL>gcc -o addtest.exe -s addtest.o -L. -ladd

z:\Users\mpayne\Documents\MinGWDLL>addtest.exe
29

This is the build process that many real world libraries use. If it’s not clear what’s going on with the preprocessor definitions, pass “-save-temps” on the gcc command line during the compilation stage and look at the files with a “.i” extension—you’ll notice that ultimately the generated code in both elegant and inelegant examples is almost identical.

Exporting and Importing Variables

In addition to functions, it’s also possible to export and import variables. These variables must be declared “extern __declspec(dllexport)” or “extern __declspec(dllimport)”, depending on whether we are building the DLL or a client application using these functions. Similarly to functions, we can use our preprocessor definitions and simply delcare the variables “extern ADDAPI”. A calling convention should not be specified for variables.

Here we have added two exported variables to our header file, foo and bar.


/* add_var.h

   Declares a function and variables to be imported by our application, and
   exported by our DLL.
*/

/* You should define ADD_EXPORTS *only* when building the DLL. */
#ifdef ADD_EXPORTS
  #define ADDAPI __declspec(dllexport)
#else
  #define ADDAPI __declspec(dllimport)
#endif

/* Define calling convention in one place, for convenience. */
#define ADDCALL __cdecl

/* Make sure functions are exported with C linkage under C++ compilers. */
#ifdef __cplusplus
extern "C"
{
#endif

/* Declare our Add function using the above definitions. */
ADDAPI int ADDCALL Add(int a, int b);

/* Exported variables. */
extern ADDAPI int foo;
extern ADDAPI int bar;

#ifdef __cplusplus
} // __cplusplus defined.
#endif

The code for the DLL now includes the assignment of values to our exported variables:


/* add_var.c

   Demonstrates creating a DLL with an exported function and imported variables.
*/

#include "add_var.h"

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

/* Assign value to exported variables. */
int foo = 7;
int bar = 41;

The application has been modified so that now it adds the imported foo and bar variables together, printing the result:


/* add_vartest.c

   Demonstrates using the function and variables exported by our DLL.
*/

#include <stdlib.h>
#include <stdio.h>

/* Don't forget to change this to #include <add.h> for real applications where
   the header has been deployed to a standard include folder!
*/
#include "add_var.h"

int main(int argc, char** argv)
{
  /* foo + bar = Add(foo, bar) */
  printf("%d + %d = %d\n", foo, bar, Add(foo, bar));

  return EXIT_SUCCESS;
}

Compilation is the same as before:

z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o add_var.o add_var.c -D ADD_EXPORTS

z:\Users\mpayne\Documents\MinGWDLL>gcc -o add_var.dll add_var.o -s -shared

z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o add_vartest.o add_vartest.c

z:\Users\mpayne\Documents\MinGWDLL>gcc -o add_vartest.exe -s add_vartest.o -L. -ladd_var

z:\Users\mpayne\Documents\MinGWDLL>add_vartest.exe
7 + 41 = 48

If you change the values of foo and bar, recompile the DLL, and run the application again, you can see that the variables are indeed imported from the DLL.

Import Libraries

Although it’s usually possible to link to a DLL simply by having the DLL present on your system and adding some linker command line options, it may not always be desirable. In this case, an import library should be used instead. An import library contains no code, but does contain all of the necessary information needed for your application to locate the functions exported by the DLL. I would recommend always creating an import library and distributing it with your DLL, as your users may have a requirement to build their applications this way.

If you have modified the function names that the DLL exports (see “Warning About Exporting stdcall Functions” below), an import library is the only option. This is the case for the 32 bit Windows API, where you must link your applications using import libraries, i.e. passing “-lgdi32 -luser32” etc. on the linker command line when compiling Windows GUI programs.

Creating and Using an Import Library

If you haven’t modified any of the function names exported by the DLL, then creating an import library is just a case of passing an extra parameter “-Wl,--out-implib,lib[library name].a” to the linker command line.

z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o add.o add.c -D ADD_EXPORTS

z:\Users\mpayne\Documents\MinGWDLL>gcc -o add.dll add.o -s -shared -Wl,--out-implib,libadd.a
Creating library file: libadd.a

It’s conventional to use the library name in the import library name. It’s mandatory that the filename begins with “lib” and ends with a “.a” extension in order for it to be usable (actually there are a few supported naming conventions you can use which are covered in the manual, but anything else won’t work).

Building the application is the same as before, but instead of passing the directory containing the DLL to the linker, you pass the directory containing the import library instead (actually we’re still doing everything from the current directory, but this likely wouldn’t be the case for a real application). To link to the DLL, you pass the name of the import library without the lib prefix and file extension.

z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o addtest.o addtest.c

z:\Users\mpayne\Documents\MinGWDLL>gcc -o addtest.exe -s addtest.o -L. -ladd

z:\Users\mpayne\Documents\MinGWDLL>addtest.exe
29

Typically DLLs go in a folder named “bin”, and import libraries go in a folder named “lib”, for example “C:\Program Files\Add\bin\” and “C:\Program Files\Add\lib\”.

Warning About Exporting stdcall Functions

One thing you should be aware of when declaring functions “__stdcall”, is that MinGW and MSVC export their functions with slightly different names (function decorations). For our add example above, MSVC would export the “Add(int a, int b)” function with the name “_Add@8” (underscore, function name, at sign, size of arguments in bytes), whereas MinGW will export it as “Add@8” (same as MSVC, but without the underscore). Therefore, if you intend to have binary compatibility between MinGW and MSVC builds of your DLL, it’s necessary to change the names of the exported functions. The only problem is that the above method of creating an import library won’t work, and the build is somewhat more complicated. There is a very helpful article Stdcall and DLL tools of MSVC and MinGW which covers this in detail, along with some of the pitfalls you should watch out for.

Exposing C++ Functionality in DLLs

I haven’t covered exposing any C++ funtionality from DLLs in this article. This is because it’s a very complicated matter, and creating a C++ DLL will almost certainly result in a DLL which is only useable by applications built with the same compiler (in fact it’s possible that the DLL and application need to be built with the same version of the same compiler, due to changes between compiler revisions). Using C++ in DLLs is fine, as long as the interface exposed by the DLL consists purely of C functions and variables.

Exposing only C functionality when using C++ means:

The point about not throwing C++ exceptions is particularly important, because it may not be immediately obvious that your exported functions could throw exceptions. If C++ exceptions are thrown across DLL boundaries, they may go unnoticed in your application—whether it be because your application is written in C, which has no knowledge of C++ exceptions, or because the DLL was built with a different compiler to the application. It’s therefore necessary to catch all exceptions inside your exported functions, and if necessary return a status code from the function to notify the caller.

It is by no means impossible to expose C++ functionality in DLLs, and I’m not suggesting you should never do things such as export C++ classes and overloaded functions, but there are many pitfalls surrounding this which you need to be aware of before attempting to do so—only a few of which have been discussed here.

Summary

This article has shown how to create Windows DLLs and import libraries, allowing creation of function libraries which can be reused throughout your applications. This article didn’t cover more advanced topics such as specifying DLL base addresses, exporting C++ classes, using module definition files, changing exported function names, or loading DLLs dynamically at runtime. Once you’ve mastered the basics, I’d recommend you research these topics too.