Exporting C++ from dlls

This post will cover how to export functions from a windows dynamic-link library starting with the basics and then covering some more advanced examples. The first part of this explanation is very similar to this msdn article, so if you already know that you may want to skip ahead to the [Advanced Exporting]({{ page.url | prepend: site.baseurl }}#advanced-exporting) section.

Basic Exporting

So let's start with the basics, to export a function from a dll you need to inform the compiler which functions and classes you want to be accessible from the library. This can be done in multiple ways but the simplest is to use __declspec(dllexport) to markup the functions or classes when declaring and __declspec(dllimport) when using them.

// function declaration in library header
//
__declspec(dllexport) void function();

// function declaration in executable source
//
__declspec(dllimport) void function();

Having to declare everything twice, once for use within the library and once for use external to the library, is not really something we want to have to do even for just one function so the common practice is to use a macro like so:

#if defined(DYNAMICLIB_EXPORTS)
# define DL_API __declspec(dllexport)
# else
# define DL_API __declspec(dllimport)
#endif

DL_API void function();
{}

When building the library DYNAMICLIB_EXPORTS is defined so the macro will be __declspec(dllexport) and when including the library the macro will be __declspec(dllimport) which is exactly what we want.

Exporting classes is pretty similar, we can export individual class methods or more commonly the class as a whole:

#if defined(DYNAMICLIB_EXPORTS)
# define DL_API __declspec(dllexport)
# else
# define DL_API __declspec(dllimport)
#endif

class SingleMethodExported
{
public:
  DL_API void Method();
};

class DL_API WholeClassExported
{
};

This is all basic stuff that has been covered many times before so we won't cover any more simple examples, time to move on to some more complex stuff.

Advanced Exporting

Now we've done that we can try doing something a bit more challenging, trying to export some code defined in a lib from a dll that links it. This is useful if our dynamic library links a third party static library that will be used in both our library and any executable that links our library. We may need to ensure that the version of the third party our dynamic library uses is the same as the one used by the executable. There are a couple of ways to do this, one uses #pragmas and one uses .def files and we're going to cover both.

First let's start with some simple example code, we'll have a static library called staticlib, a dynamic library called dynamiclib and an executable that needs to use both called application.

In our staticlib we have some functions declared like this:

#ifndef SL_API
# define SL_API
#endif

extern "C"
{
SL_API void slFunction();
};

namespace sl
{
SL_API void slFunctionInNamespace();
} // namespace sl

Note that we have a macro SL_API that is defined as nothing. This is useful if we ever want to switch the library from being static to dynamic but is not necessary for either of the techniques covered here.

In our dynamiclib we have some functions declared like this:

#if defined(DYNAMICLIB_EXPORTS)
# define DL_API __declspec(dllexport)
#else
# define DL_API __declspec(dllimport)
#endif

extern "C"
{
DL_API void dlFunction();
};

namespace dl
{
DL_API void dlFunctionInNamespace();
} // namespace dl

Declaring a function within an extern "C" section means that the compiler won't do any name mangling. We're going to look at the differences between a name mangled function and a plain C function and see why it's much easier to try and export plain C functions.

This example code is very simple and there is no harm in application directly linking with staticlib but for the purposes of this example we'll pretend we still need to export everything from dynamiclib. If staticlib contained functions that allocated and deallocated objects and these functions were called from the dynamiclib and the application then there is a chance for a mismatched new/delete or malloc/free. By exporting staticlib functionality from dynamiclib we prevent any chance of a mismatch as all definitions will come from dynamiclib.

It would be nice if defining the SL_API macro as __declspec(dllexport) when compiling dynamiclib and defining it as __declspec(dllimport) when using dynamiclib was enough to export the staticlib functions. Something like this:

#if defined(DYNAMICLIB_EXPORTS)
# define SL_API __declspec(dllexport)
#else
# define SL_API __declspec(dllimport)
#endif

#include <sl/slFunction.h>

Unfortunately it's not, if we try and compile our application like this we get some LNK2019: unresolved external symbol linker errors. When compiling in Visual Studio 2012 with no optimisation and generating a program database for edit and continue these errors will look something like this:

error LNK2019: unresolved external symbol _slFunction referenced in function _main
error LNK2019: unresolved external symbol "void __cdecl sl::slFunctionInNamespace(void)" (?slFunctionInNamespace@sl@@YAXXZ) referenced in function _main

#pragma exporting

The first method we're going to cover is using #pragmas to export. Using #pragma comment(linker, "") we can hard code linker options inside an object file, specifically the /export option.

So to export these unresolved external symbols from dynamiclib we need to add another source file with some #pragma statements in. For our simple C function void slFunction() this is simple as the symbol name is the same as the function name with an underscore prepended. For our C++ function namespace sl { void slFunctionInNamespace(); } it's a bit more complicated as the symbol name has been mangled by the compiler. There are several ways of finding out the symbol name of a mangled function but in this case we're going to use linker error message which handily also included the symbol name it was looking for.

Looking again at the error messages we can see for the name mangled function the symbol was reported with part of it in quotes "void __cdecl sl::slFunctionInNamespace(void)" and a second part in brackets (?slFunctionInNamespace@sl@@YAXXZ), we're interested in the part in brackets as this is the name after it has been mangled.

Now we have both symbol names we can fill in our source file to export these functions:

#pragma comment(linker, "/export:_slFunction")
#if (_MSC_VER == 1700)
# if _DEBUG
#  pragma comment(linker, "/export:?slFunctionInNamespace@sl@@YAXXZ")
# endif
#endif

Just to be safe there are some extra guards around the #pragma for the name mangled function. Name mangling is dependent on compiler options and versions of the compiler so this specific match will only work with Visual Studio 2012 builds that generated debug symbols.

Now, as long as it is compiled with the correct compiler version and options, our application will successfully link and use the versions of the staticlib functions exported from dynamiclib.

.def file exporting

A slightly less intrusive way of doing the same thing is to use a .def file, less intrusive as it only requires an additional .def file when linking dynamiclib. The .def file contains a list of symbols names to export so we can simply add the staticlib symbol names we need:

LIBRARY dynamiclib
EXPORTS
  slFunction                        @1
  ?slFunctionInNamespace@sl@@YAXXZ  @2

Again this is relying on the mangled symbol name for the C++ function so we would need a different def file for every configuration of dynamiclib we decide to build. Assuming the .def file is called dynamiclib.def when linking dynamiclib we need to add the linker option /DEF dynamiclib.def and application will now successfully link.

Finding mangled symbol names

At the moment the only way we have of finding mangled symbol names is to rely on linker error messages which, although fine in this example, is a pretty slow method if we were exporting an entire third party library.

One alternative is to use the command dumpbin included with visual studio. Using the command line dumpbin /linkermember:1 staticlib.lib, the generated output contains a public symbols section that looks something like this:

4 public symbols

  24A ??_C@_0CD@MJKDCNBD@called?5slFunction?5from?5staticlib@
  24A ??_C@_0DC@JOPHPFLM@called?5sl?3?3slFunctionInNamespace@
  24A ?slFunctionInNamespace@sl@@YAXXZ
  24A _slFunction

Using a script to parse the output of this file looking for the pattern ?function@namespace a .def file or file containing the necessary #pragmas could be auto generated as a pre build step saving the tedious process of manually generating them from linker errors.

Another potential method would be to parse a generated pdb file using something like pdbparser to pull out the symbol names and auto generate the required files this way.

Combining either of these methods with a C++ parser you could generate the necessary files given a set of input header files. A C++ command line application using clang and the pdbparser or a script that uses clang‘s python bindings and dumpbin would be ideal but writing them is not a trivial process.

Summary

So after a quick gloss over the basics of dll exporting we have learnt the more complex process of exporting static library code from a dynamic library. If the static library only deals with plain C functions the process is relatively simple and really only requires the names of all the functions that should be exported. If the static library has C++ functions or classes, we didn't cover those but they require a name mangled function for every class method to be exported, then it is a lot harder due to the name mangling done by the compiler. This process could be automated given a list of input names and using dumpbin or parsing a .pdb but we've not covered that and it is non trivial. If this is ever needed in the real world then hope the third party library only uses C functions and there is no need to generate symbols for every configuration.

For a working example of the techniques discussed in this post there is a repository on github https://github.com/fun4jimmy/dllexport-snippet. The repository only contains project files for Visual Studio 2012 though there is a Premake4 script to generate projects for other Visual Studio versions. Other version of Visual Studio will require different mangled names so the .def file and the .cpp file containing the #pragmas will need modifying.