Transmission Zero

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, so you or your end users can 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 likely misbehave or crash as a result of 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 usual 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 -Wl,--subsystem,windows

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 -Wl,--subsystem,windows”. The “-Wl,--subsystem,windows” isn’t really necessary, but it’s just conventional that DLLs have the Windows GUI subsystem specified in their PE header. Also note the “-s” switch is used to strip symbols 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 would cause the linker step to fail.

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:

#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

The code for the DLL can now include the header we just created, and the only change we need to make is to include “ADDCALL” before the function name (it’s necessary to specify the calling convention here, to prevent possible conflicts between the declaration and definition of the function).

/* 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:

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,--subsystem,windows

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>
#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 variables. 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 -Wl,--subsystem,windows

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. When creating libraries for third parties to use, I would recommend always creating an import library and distributing it with your DLL and header files, as your users may have a requirement to build their applications using import libraries.

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 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,--subsystem,windows,--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. This is a bit more advanced than I intend to cover in this article, but I have written an Advanced MinGW DLL Topics article which explains how to do this, along with some of the pitfalls you should watch out for.

Adding Version Information and Comments to your DLL

If you view the properties for a DLL using Windows explorer and go to the “details” tab, you’ll likely see information about the DLL such as the version, author, copyright, and a description of the library. It’s a nice touch to add to a DLL (particularly helpful when you want to know which version of a DLL you have installed on your PC), and is fairly straightforward to add to your DLL. All you need to do is create a version resource such as the following:

#include <windows.h>

// DLL version information.
VS_VERSION_INFO    VERSIONINFO
FILEVERSION        1,0,0,0
PRODUCTVERSION     1,0,0,0
FILEFLAGSMASK      VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
  FILEFLAGS        VS_FF_DEBUG | VS_FF_PRERELEASE
#else
  FILEFLAGS        0
#endif
FILEOS             VOS_NT_WINDOWS32
FILETYPE           VFT_DLL
FILESUBTYPE        VFT2_UNKNOWN
BEGIN
  BLOCK "StringFileInfo"
  BEGIN
    BLOCK "080904b0"
    BEGIN
      VALUE "CompanyName", "Transmission Zero"
      VALUE "FileDescription", "A library to perform addition."
      VALUE "FileVersion", "1.0.0.0"
      VALUE "InternalName", "AddLib"
      VALUE "LegalCopyright", "©2013 Transmission Zero"
      VALUE "OriginalFilename", "AddLib.dll"
      VALUE "ProductName", "Addition Library"
      VALUE "ProductVersion", "1.0.0.0"
    END
  END
  BLOCK "VarFileInfo"
  BEGIN
    VALUE "Translation", 0x809, 1200
  END
END

I won’t go into detail about what each of these means, as it’s covered very well in the MSDN, but most of it is self-explanatory (I’d suggest reading the MSDN article anyway, particularly if the language is American English or anything other than British English).

The idea is to compile the resource script using windres.exe, and then pass it to the linker when linking your DLL. For example if your resource script is named “resource.rc”:

z:\Users\mpayne\Documents\MinGWDLL>windres -i resource.rc -o resource.o

z:\Users\mpayne\Documents\MinGWDLL>gcc -o add.dll add.o resource.o -s -shared -Wl,--subsystem,windows,--out-implib,libadd.a

That’s all there is to adding version information to a DLL. It’s worth taking the time to do this for any DLLs you release, as it’s both helpful and gives your application a professional look and feel.

Putting It All Together

Using the information in this article, you should now be able to put it all together and create DLLs using MinGW, as well as executables which use the functions exported from the DLLs. To help you out, I’ve created a small project which builds a DLL with the following functionality:

Additionally an executable will be built which prints the result of an addition, using the exported function and exported variables. The project can be built with the mingw32-make utility.

You can download the MinGW DLL Example from GitHub to build the example DLL and application from this article. You can either git clone the repository or download a MinGW DLL Example release. You are free to use it for whatever purpose you see fit (see the “License.txt” for the full terms of use). Using the project files, you can easily build the example from this article, and use it as a basis for your own DLLs and applications.

Things To Be Aware Of

There are a few things to be aware of when exposing a C API from a DLL. Firstly, your code can’t throw any C++ exceptions inside the DLL and expect the client application to catch these exceptions. This shouldn’t be a problem if you are using pure C code because C doesn’t support C++ exceptions, but if you’re coding in C++ and are specifying C linkage for your exported functions, it’s something you need to be careful about. It’s best to catch all exceptions where there is a possibility of an exception being thrown, and return some error code to the caller of the API you’re exposing. If you do throw exceptions across a DLL boundary, you might get lucky and it will work for you, but it will more than likely fail if someone is using your library with a different programming language, a different compiler, or even a different version of the compiler you are using (just because you wrote the library in C, doesn’t mean that someone won’t use it, for example, in an application written in Visual Basic, assembly, D, or C#).

Another thing to be aware of is that if you’re passing structs into a function exported by a DLL, or returning a struct from that function, you can’t later change the definition of that struct. Doing such changes would likely result in older applications either crashing or behaving in unintended ways, because the memory layout of the struct has changed since the application was compiled. It’s not impossible to change the definition of a struct without breaking backwards compatibility, but it’s not elegant either. For example, it’s common in the Windows API that structs have a (seemingly pointless) member which indicates the size of the struct, have new members added at the end of the struct definition, and are passed into functions by address and not value (for example the OPENFILENAME struct). These are the kind of things that allow the Windows API to change between releases of Windows without breaking backwards compatibility (I know people might say that each version of Windows causes many backwards compatibility issues, but the fact is that a well written application created for Windows 95 has a good chance of running on recent versions of Windows, which in my opinion is pretty impressive).

Although it’s possible to export C++ variables and C++ functions from a DLL, you should avoid doing so unless you really understand what you are doing. C++ does not have the well defined ABI that C does, and you’ll likely find that compiled C++ functions created with a compiler from one vendor are not compatible with those from another vendor. In fact, you may find that compatibility is broken between different versions of a C++ compiler from the same vendor. For this reason, each compiler has it’s own way of mangling the names of C++ functions, which prevents functions created with different C++ compilers from being linked together. As disappointing as this is, it prevents potential mayhem which could occur as a result of differences in ABI. I cover C++ DLLs in my Advanced MinGW DLL Topics article.