Transmission Zero

Advanced MinGW DLL Topics



Introduction

This article is a follow-on from my Building Windows DLLs with MinGW article, which gave an introduction into building DLLs with MinGW and using them within an executable. In this article I will cover some more advanced topics which weren’t appropriate for the introductory article:

  1. Displaying functions exported from a DLL.
  2. The DllMain function.
  3. Using a module definition file.
  4. Exporting undecorated stdcall functions.
  5. Exporting C++ functions and variables from a DLL.
  6. Creating JNI DLLs.
  7. P/Invoking MinGW DLLs in .NET
  8. Setting the DLL base address.
  9. Loading and unloading DLLs at runtime.

Displaying Functions Exported From A DLL

If you’d like to view the functions exported by a DLL, you can use the GNU binutils objdump tool. This is useful if you want to see the exact symbols which are being exported from a DLL, including any name mangling. It is also useful if you have a third party DLL and would like to see exactly what it’s exporting. The following is an example of dumping the exports of the “AddLib.dll” from my previous article:

z:\Users\mpayne\Documents\MinGWDLL>objdump -p AddLib.dll

The output will contain something like the following:

There is an export table in .edata at 0x6da46000

The Export Tables (interpreted .edata section contents)

Export Flags                    0
Time/Date stamp                 4da9a500
Major/Minor                     0/0
Name                            00006046 AddLib.dll
Ordinal Base                    1
Number in:
        Export Address Table            00000003
        [Name Pointer/Ordinal] Table    00000003
Table Addresses
        Export Address Table            00006028
        Name Pointer Table              00006034
        Ordinal Table                   00006040

Export Address Table -- Ordinal Base 1
        [   0] +base[   1] 1280 Export RVA
        [   1] +base[   2] 2004 Export RVA
        [   2] +base[   3] 2000 Export RVA

[Ordinal/Name Pointer] Table
        [   0] Add
        [   1] bar
        [   2] foo

You can see the function “Add” is being exported, along with the variables “foo” and “bar”. You don’t get any more information than this, for example the number of parameters and their types. In fact, you don’t even know if a name represents a function or variable. That’s because each name represents an address in memory, which could be a pointer to anything.

If you have the Microsoft Windows SDK, you can also use the dumpbin utility to display exported functions:

z:\Users\mpayne\Documents\MinGWDLL>dumpbin -exports AddLib.dll
Microsoft (R) COFF/PE Dumper Version 9.00.30729.01
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file AddLib.dll

File Type: DLL

  Section contains the following exports for AddLib.dll

    00000000 characteristics
    4DA9A500 time date stamp Sat Apr 16 15:17:36 2011
        0.00 version
           1 ordinal base
           3 number of functions
           3 number of names

    ordinal hint RVA      name

          1    0 00001280 Add
          2    1 00002004 bar
          3    2 00002000 foo

  Summary

        1000 .CRT
        1000 .bss
        1000 .data
        1000 .edata
        1000 .eh_fram
        1000 .idata
        1000 .rdata
        1000 .reloc
        1000 .rsrc
        1000 .text
        1000 .tls

As you can see, it’s the same information about the exported functions, but presented in a different format.

The DllMain Function

DLLs can have an entry point named “DllMain”, which is called by the operating system when the DLL is loaded into or unloaded from memory, or when a thread is created or has exited. The format of the functions is as follows:

#include <windows.h>

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
  switch (fdwReason)
  {
    case DLL_PROCESS_ATTACH:
      /* Code path executed when DLL is loaded into a process's address space. */
      break;

    case DLL_THREAD_ATTACH:
      /* Code path executed when a new thread is created within the process. */
      break;

    case DLL_THREAD_DETACH:
      /* Code path executed when a thread within the process has exited *cleanly*. */
      break;

    case DLL_PROCESS_DETACH:
      /* Code path executed when DLL is unloaded from a process's address space. */
      break;
  }

  return TRUE;
}

Note that the function signature should match exactly, as getting the function name or case incorrect will result in the function never being called, and getting the calling convention incorrect can corrupt the stack—no matter what calling convention your other functions are using, always mark the DllMain function WINAPI, which maps to stdcall on Win32. The return value is always required (as per the C standard), but is ignored for all messages except for “DLL_PROCESS_ATTACH”. When handling the process attach message, you should return TRUE if your initialisation was successful, or FALSE if initialisation was unsuccessful. In the case that initialisation failed, the application will fail to start up and issue an error if the DLL is statically loaded, and in the case of dynamic loading, it will cause “LoadLibrary” to return a NULL value, and the DLL will not be mapped into the address space of the process.

The DLL entry point should perform only basic initialisation procedures for your DLL. If you need to perform other tasks, this should be handled elsewhere with a function called from the user code. This is because there is no guarantee about the order in which DLLs are loaded, so if your entry point for “Foo.dll” calls a function in “Bar.dll”, it’s quite possible that Bar.dll has not yet been loaded into your process’s address space, and would result in a general protection fault. The only functions which are safe to call are those located in “kernel32.dll”, which is guaranteed to be loaded before your DLL is loaded. See the DllMain Entry Point MSDN article for full details of how it works, and what you should and shouldn’t do in this function.

Using A Module Definition File

An alternative to using the “__declspec(dllexport)” attribute on functions you want exported from a DLL, is to use a module definition file. This is simply a file which defines (among other things), the functions and variables you want exported from a DLL. An example for our “AddLib.dll” looks like the following:

LIBRARY AddLib.dll
EXPORTS
  Add
  foo
  bar

This specifies that we want function “Add” being exported from the DLL, along with variables “foo” and “bar”. Our C header file looks like the following:

/* 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. */
int ADDCALL Add(int a, int b);

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

#ifdef __cplusplus
} // __cplusplus defined.
#endif

You can see that we have not exported the function or the variables. However, we still need to specify the calling convention, and the variables still to be marked “extern” to ensure they are linked correctly. The function body is implemented as follows:

#include "add.h"

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

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

To use the module definition file, you just have to pass the filename during the link step:

z:\Users\mpayne\Documents\MinGWDLL>gcc -O3 -std=c99 -Wall -c add.c -o add.o

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

One thing to be aware of, is that the header file above does not declare the functions “__declspec(dllimport)” when they are being imported. Whilst this does still work, it results in a slight overhead when calling these functions, as the compiler is not able to make an optimisation that it would otherwise be able to make if it knew the function was coming from a DLL. You could add a conditionally defined “__declspec(dllimport)” to the header file for the exported functions, but this adds another layer of complexity.

Exporting Undecorated stdcall Functions

If you are exporting functions from a DLL and you’re using the stdcall calling convention, you’ll find that MinGW decorates the functions exported from the DLL. For example if you have a function with the signature “int Add(int, int)”, the exported name will be “Add@8”. The linker has added an ‘@’ sign after the function name, followed by the size of the function arguments in bytes. However, if you’re using Microsoft’s Visual C++ you’ll find that it additionally prefixes the name with an underscore, for example “_Add@8”. This may not seem to be a cause for concern, but consider the following scenario. You create an image handling library, and distribute it as source code only. Company A builds an application using your image library, and builds them using MSVC. It deploys the DLL to the “system32” folder of your system, and all works fine. Company B then builds an application using your image library, and builds them using MinGW. It also deploys the DLL to the “system32” folder, overwriting the version of the DLL that Company A deployed. Company B’s application works fine, but Company A’s application is now broken, because the MSVC and MinGW DLLs have differently named exports which are incompatible with each other. The solution is to export the functions without decorations.

To export functions without decorations, it’s simply necessary to pass “--kill-at” to the linker. You must also create an import library when modifying the names exported by a DLL, otherwise the link step will fail when building applications using your DLL. Unfortunately, passing “--out-implib” to the linker will result in an unusable import library, due to the way that the functions are exported. To create the import library, you will need to use dlltool.exe along with a module definition file which contains the decorated versions of all of the exported functions. If this all sounds like a real pain, the following is a much simpler (if slightly inelegant) way of doing this:

z:\Users\mpayne\Documents\MinGWDLL>gcc -o AddLib.dll add.o -shared -s -Wl,--subsystem,windows,--output-def,AddLib.def

z:\Users\mpayne\Documents\MinGWDLL>gcc -o AddLib.dll add.o -shared -s -Wl,--subsystem,windows,--kill-at

z:\Users\mpayne\Documents\MinGWDLL>dlltool --kill-at -d AddLib.def -D AddLib.dll -l libaddlib.a

The first step creates the DLL with decorated functions. Although this is not what we want, passing “--output-def,AddLib.def” to the linker generates a module definition file for us which contains decorated function names. The second step creates the DLL again, but this time we pass “--kill-at” to export the functions without decorations. Note that we can’t create the module definition file in this step, because it would contain undecorated function names. The third and final step creates the import library, based on the module definition file and the DLL name. A little more complicated, but worth it if you need consistency between compilers. In fact, the Win32 API functions are exported this way, without any decorations.

Exporting C++ Functions & Variables

Exporting C++ functions and variables from a DLL is not particularly difficult, but there is one thing which can catch you out if you’re not aware. If you build a C++ DLL with one compiler, it probably won’t work with another compiler. In fact, C++ DLLs built with a certain version of a compiler may not be compatible with DLLs built with another version of the same compiler. This is because C++ is a complex language, and doesn’t have the well defined ABI which C has. For example, different compilers might have different ways of handling C++ exceptions, different ways of implementing virtual functions, or different memory layouts for STL classes. To avoid the issues which would result from this, C++ implementations are intentionally incompatible, and use different methods of name mangling for their functions.

Exporting global variables and global functions from a DLL is done the same way in C++ as it is done in C. The only difference from C, is that global variables can be instances of C++ objects, and the functions can be overloaded if needed. You can also export a C++ class, which will export all of its static and instance member functions which have implementations, whether they are public, protected, or private. The following is an example of a (very minimal) class representing a 2D point:

#ifndef POINT_HPP
#define POINT_HPP

#ifdef POINT_EXPORTS
  #define POINTAPI __declspec(dllexport)
#else
  #define POINTAPI __declspec(dllimport)
#endif

#include <ostream>

using std::ostream;

class POINTAPI Point
{
  public:
    // Constructors.
    Point();
    Point(int x, int y);

    // Getters and setters.
    int getX() const;
    int getY() const;
    void setX(int x);
    void setY(int y);

    // Friend the overloaded operators, so they can access private Point data.
    friend Point operator+(const Point& lhs, const Point& rhs);
    friend ostream& operator<<(ostream& os, const Point& pt);

  private:
    int x, y;
};

// Overloaded operators.
POINTAPI Point operator+(const Point& lhs, const Point& rhs);
POINTAPI ostream& operator<<(ostream& os, const Point& pt);

extern POINTAPI Point foo;
extern POINTAPI Point bar;

#endif

The point class has been exported, which means that the two constructors, the getters, and the setters are exported. Additionally, the global function to add two points together, the global function to write the point to a stream, and two instances of points have been exported from the DLL. The following is the implementation of these functions:

#include "point.hpp"

Point::Point()
  : x(0), y(0)
{ }

Point::Point(int x, int y)
  : x(x), y(y)
{ }

int Point::getX() const { return this->x; }

int Point::getY() const { return this->y; }

void Point::setX(int x) { this->x = x; }

void Point::setY(int y) { this->y = y; }

Point operator+(const Point& lhs, const Point& rhs)
{
  return Point(lhs.x + rhs.x, lhs.y + rhs.y);
}

ostream& operator<<(ostream& os, const Point& pt)
{
  return os << "(" << pt.x << ", " << pt.y << ")";
}

Point foo(9, 6);
Point bar(3, 19);

Typically such simple functions would be declared inline, but we haven’t done so here because it would defeat the purpose of housing the functions in a DLL. Building the DLL is much the same as building a C DLL, except that we are using the C++ compiler in this case:

z:\Users\mpayne\Documents\MinGWDLL>g++ -c -o point.o point.cpp -D POINT_EXPORTS

z:\Users\mpayne\Documents\MinGWDLL>g++ -o point.dll point.o -s -shared -Wl,--subsystem,windows,--out-implib,libpoint.a
Creating library file: libpoint.a

Creating the import library is optional, as MinGW can alternatively use the DLL in the link step instead of the import library. If you perform an “objdump -p” on the DLL, you will notice that the functions have names such as “_ZN5Point4setXEi” and “_ZlsRSoRK5Point”. If it isn’t clear what these mangled functions refer to, you can use the “c++filt” tool to unmangle them. If you pass a function name on the command line, it will unmangle the name, and if you pass nothing on the command line, it will wait for names to be written to the standard input:

z:\Users\mpayne\Documents\MinGWDLL>c++filt -n _ZlsRSoRK5Point
operator<<(std::basic_ostream<char, std::char_traits<char> >&, Point const&)

z:\Users\mpayne\Documents\MinGWDLL>c++filt -n
_ZN5Point4setXEi
Point::setX(int)
_ZN5Point4setYEi
Point::setY(int)
_ZN5PointC1Eii
Point::Point(int, int)
_ZN5PointC1Ev
Point::Point()
_ZN5PointC2Eii
Point::Point(int, int)
_ZN5PointC2Ev
Point::Point()
_ZNK5Point4getXEv
Point::getX() const
_ZNK5Point4getYEv
Point::getY() const
_ZlsRSoRK5Point
operator<<(std::basic_ostream<char, std::char_traits<char> >&, Point const&)
_ZplRK5PointS1_
operator+(Point const&, Point const&)

Now that the DLL is created, you can write an application which makes use of the DLL. The following example makes use of all of the exported functions, and makes use of the overloaded addition operator along with the stream operator to add two points, and write them to the screen:

#include <iostream>
#include "point.hpp"

using namespace std;

int main(int argc, char** argv)
{
  Point a;
  Point b(2, 7);
  Point c;
  
  c.setX(85);
  c.setY(24);
  
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  cout << "c = (" << c.getX() << ", " << c.getY() << ")\n";

  cout << "foo + bar = " << foo << " + " << bar << " = " << (foo + bar) << endl;

  return 0;
}

The application can then be compiled, linked, and executed:

z:\Users\mpayne\Documents\MinGWDLL>g++ -c -o pointtest.o pointtest.cpp

z:\Users\mpayne\Documents\MinGWDLL>g++ -o pointtest.exe -s pointtest.o -L. -lpoint

z:\Users\mpayne\Documents\MinGWDLL>pointtest.exe
a = (0, 0)
b = (2, 7)
c = (85, 24)
foo + bar = (9, 6) + (3, 19) = (12, 25)

In this case we have exported all of the members from a class, but you don’t have to export them all if you don’t want to. Instead, you can mark individual member functions as being exported. Again, it doesn’t matter whether they are static functions or instance member functions, and it doesn’t matter whether they are public, protected, or private. The only thing you cannot export is a pure virtual function, which by definition has no implementation, so there is nothing to export. Regular virtual functions can be exported, but it’s only necessary to export them if you need to call them for stack based variables which are not accessed through a pointer or reference—for variables which are accessed via a pointer or reference, the functions are instead looked up in the object’s vtable, so a DLL import is not used.

When deploying C++ DLLs, you need to be very careful to avoid breaking other applications by putting an incompatible DLL in another application’s path. It’s best to either place the DLL in the same directory as the application you are deploying, or if you need to put the DLL in a shared folder, give the DLL a name which is unique to the compiler and version. An example would be “point-mingw-4.5.2.dll", or “point-msvc-2010.dll”.

Creating JNI DLLs

Creating MinGW DLLs for use with the Java Native Interface is quite easy, except for one tiny gotcha. The JNI calling convention on Win32 is stdcall, and the JVM expects the exported functions to either have undecorated names, or have names decorated in the format “_[function name]@[size of arguments]”. MinGW does not prefix the names with an underscore, so trying to call MinGW DLLs from Java might result in a stack trace like the following:

z:\Users\mpayne\Documents\MinGWDLL>java Hello
Exception in thread "main" java.lang.UnsatisfiedLinkError: Hello.add(II)I
        at Hello.add(Native Method)
        at Hello.main(Hello.java:5)

The solution is to ensure that the functions are exported with undecorated names. As we only require a DLL to be created in this case (no import library is needed), you can simply pass “--kill-at” to the linker and you’re done. Here is a quick example of using JNI with MinGW. Firstly, create the Java file with a native function declaration:

public class Hello
{
  public static void main(String[] args)
  {
    System.out.println("8 + 5 = " + Hello.add(8, 5));
  }
  
  static
  {
    System.loadLibrary("Hello");
  }
  
  public static native int add(int a, int b);
}

Remember to call “System.loadLibrary()” for the DLL, as I have done in the static constructor above. You can then compile the Java source code and create the C / C++ header file:

z:\Users\mpayne\Documents\MinGWDLL>javac Hello.java

z:\Users\mpayne\Documents\MinGWDLL>javah Hello

The second command will create a C / C++ header file which looks like the following:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */

#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Hello
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_Hello_add
  (JNIEnv *, jclass, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

Next, create a C file which contains the body of this function:

#include "Hello.h"

jint JNICALL Java_Hello_add(JNIEnv* env, jclass clazz, jint a, jint b)
{
  return (a + b);
}

Now you can compile the DLL, remembering to pass “--kill-at” to the linker, and run the application:

z:\Users\mpayne\Documents\MinGWDLL>gcc -c -o Hello.o Hello.c -I "c:\Program Files (x86)\Java\jdk\include\win32" ^
                                   -I "c:\Program Files (x86)\Java\jdk\include"

z:\Users\mpayne\Documents\MinGWDLL>gcc -o Hello.dll -s -shared Hello.o -Wl,--subsystem,windows,--kill-at

z:\Users\mpayne\Documents\MinGWDLL>java Hello
8 + 5 = 13

Notice that I have passed two include paths to the compiler, as they contain headers which contain data types and function prototypes needed by JNI. You might need to adjust the paths to match those of your Java installation. Also be aware that like any Windows process, a 32 bit JVM can only load 32 bit DLLs, and a 64 bit JVM can only load 64 bit DLLs. If you try to load a 32 bit DLL in a 64 bit JVM, you’ll get an error such as “Can't load IA 32-bit .dll on a AMD 64-bit platform”.

P/Invoking MinGW DLLs in .NET

Calling functions from a MinGW DLL from .NET code is even easier than working with JNI. This is because in JNI the DLL has to be designed to work with JNI, and must follow certain rules. With P/Invoke, it doesn’t really matter how the DLL has been designed, as you can adjust the DLL import attribute in your .NET code to match the DLL.

To import a function from a DLL, you must declare the function as “extern”, and mark it with the “DllImport” attribute. Here is an example of importing a cdecl function:

using System;
using System.Runtime.InteropServices;

public class Hello
{
  public static void Main(string[] args)
  {
    Console.WriteLine("8 + 5 = {0}", Hello.Add(8, 5));
  }
  
  [DllImport("AddLib.dll", CallingConvention = CallingConvention.Cdecl)]
  extern public static int Add(int a, int b);
}

You can also import stdcall functions, by changing the DLL import attribute to specify “CallingConvention = CallingConvention.StdCall”. The .NET CLR will attempt to import the function without decorations, and if you have not specified “ExactSpelling = true”, it will also attempt to import the function decorated in the MSVC way, i.e. “_Add@8”. If you have a stdcall function decorated in the MinGW way, i.e. “Add@8”, it won’t work. However, you can still import the function by explicitly specifying the entry point:

using System;
using System.Runtime.InteropServices;

public class Hello
{
  public static void Main(string[] args)
  {
    Console.WriteLine("8 + 5 = {0}", Hello.Add(8, 5));
  }
  
  [DllImport("AddLib.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "Add@8", ExactSpelling = true)]
  extern public static int Add(int a, int b);
}

Remember that 64 bit Windows processes can only load 64 bit DLLs, and 32 bit Windows processes can only load 32 bit DLLs. Therefore if your DLL is a 32 bit DLL, and your end user is running the .NET application on a 64 bit version of Windows, your application will fail with an error similar to the following:

z:\Users\mpayne\Documents\MinGWDLL>Hello.exe

Unhandled Exception: System.BadImageFormatException: An attempt was made to load a program with an incorrect format.
(Exception from HRESULT: 0x8007000B)
   at Hello.Add(Int32 a, Int32 b)
   at Hello.Main(String[] args)

To work around this, you can specify the platform the application will run on by passing the “platform” linker option:

z:\Users\mpayne\Documents\MinGWDLL>csc /platform:x86 /out:Hello.exe Hello.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.4927
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

The platform target option can also be set in Visual Studio, in the “build” section of the project’s properties. By setting this option, your application will run correctly on both 32 bit versions of Windows and 64 bit versions. The same can be done for 64 bit DLLs, by specifying the platform as “x64”, but of course your application won’t run on 32 bit version of Windows in that case.

Using MinGW DLLs with VB6 and VBA

It’s fairly straightforward to use MinGW DLLs with Visual Basic 6 and VBA. The only requirement is that the calling convention for exported functions is stdcall (cdecl and other calling conventions are not supported), and the functions are exported without decorations by using the “--kill-at” command line option:

z:\Users\mpayne\Documents\MinGWDLL>gcc -o AddLib.dll add.o -shared -s -Wl,--subsystem,windows,--kill-at

The function needs to be declared in your code before you are able to call it:

Private Declare Function MyAddFunction Lib "AddLib.dll" Alias "Add" (ByVal a As Long, ByVal b As Long) As Long

Sub Test()
    Call MsgBox(MyAddFunction(4, 5))
End Sub

I have used the “Alias” keyword here to import the function with a different name. I have done this just to demonstrate its use, and normally you would just leave this keyword out completely and use the name exported from the DLL. It does come in useful either if you would end up with name clashes in your code, or if you are calling a Windows API function which comes in Unicode and ANSI variants, and you don’t want to suffix the ‘A’ on every single function call (note that Visual Basic only supports ANSI strings, not Unicode).

If you are using VBA, you should additionally mark your function with the “PtrSafe” keyword. This will ensure you are able to run the code on 64 bit versions of Microsoft Office. However, this is not backwards compatible with versions before Office 2010, so you will need to do it conditionally:

#If VBA7 Then
    Private Declare PtrSafe Function MyAddFunction Lib "AddLib.dll" Alias "Add" (ByVal a As Long, ByVal b As Long) As Long
#Else
    Private Declare Function MyAddFunction Lib "AddLib.dll" Alias "Add" (ByVal a As Long, ByVal b As Long) As Long
#End If

Sub Test()
    Call MsgBox(MyAddFunction(4, 5))
End Sub

It’s a bit of a pain, but well worth doing to ensure your code works across many versions of Office on different architectures.

Setting The DLL Base Address

The base address of a DLL is the preferred location within a process’s virtual address space where the DLL should be loaded. If two DLLs which are loaded by a process have the same base address, or if the regions of memory that the DLLs should be mapped into overlap, it is not possible load both DLLs at their preferred load address. Therefore, one of the DLLs needs to be relocated and loaded at a different memory address. This involves the loader patching hard coded memory addresses within the application’s address space, which can involve significant overhead. By default, the MinGW linker chooses a base address based on a hash of the DLL name, which seems to work very well. However, if you do experience issues with DLLs being unable to be loaded at their preferred address, you can specify the base address of a DLL manually with the “--image-base” linker option. For example, to set the base address to “0x10000000” you pass this option as follows:

z:\Users\mpayne\Documents\MinGWDLL>gcc -o AddLib.dll obj/add.o -shared -s ^
                                   -Wl,--subsystem,windows,--out-implib,libaddlib.a,--image-base,0x10000000

You can verify the base address using “objdump -p AddLib.dll”, which should include the “ImageBase” in an output similar to the following:

AddLib.dll:     file format pei-i386

Characteristics 0x230e
        executable
        line numbers stripped
        symbols stripped
        32 bit words
        debugging information removed
        DLL

Time/Date               Tue Apr 19 16:32:45 2011
Magic                   010b    (PE32)
MajorLinkerVersion      2
MinorLinkerVersion      21
SizeOfCode              00000c00
SizeOfInitializedData   00002200
SizeOfUninitializedData 00000200
AddressOfEntryPoint     000010c0
BaseOfCode              00001000
BaseOfData              00002000
ImageBase               10000000
SectionAlignment        00001000
FileAlignment           00000200
MajorOSystemVersion     4
MinorOSystemVersion     0
MajorImageVersion       1
MinorImageVersion       0
MajorSubsystemVersion   4
MinorSubsystemVersion   0
Win32Version            00000000
SizeOfImage             0000c000
SizeOfHeaders           00000400
CheckSum                0000383c
Subsystem               00000002        (Windows GUI)
DllCharacteristics      00000000
SizeOfStackReserve      00200000
SizeOfStackCommit       00001000
SizeOfHeapReserve       00100000
SizeOfHeapCommit        00001000
LoaderFlags             00000000
NumberOfRvaAndSizes     00000010

Loading / Unloading DLLs At Runtime

Loading a DLL at runtime is useful when you have a plugin architecture where developers can extend the functionality of your application. You would likely provide some header files which define the data structures the application uses, some prototypes for supporting functions, and some prototypes for function(s) which the plugin must implement. To take a very simple example, you might require a developer to provide a DLL with a single exported function, with a signature “void __cdecl DoPlugin();”. Your application then just needs to call the “DoPlugin” function exported by that DLL, in order to expose its functionality.

Of course, you don’t know the name of this DLL in advance, so you need to load the DLL at runtime. This is done using the “LoadLibrary” Windows API function, located in “kernel32.dll”. Calling LoadLibrary loads the DLL into the process’s address space (if it is not already present), and increments the usage count for this DLL. If the DLL is successfully loaded, the function returns an HMODULE whose value is the address in memory where the DLL was loaded (ideally the DLL base address). Once the DLL is loaded into memory, you can call the “GetProcAddress” function to get the address of the function you’d like to call. The following example attempts to load the “AddLib.dll” library from my previous article, and call the “Add” function which the DLL exports:

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

/* Function signature of the function exported from the DLL. */
typedef int (__cdecl *AddFunc)(int a, int b);

int main(int argc, char** argv)
{
  HMODULE hAddLib;
  AddFunc Add;

  /* Attempt to load the DLL into the process's address space. */
  if (! (hAddLib = LoadLibrary(TEXT("AddLib.dll"))))
  {
    fprintf(stderr, "Error loading \"AddLib.dll\".\n");
    return EXIT_FAILURE;
  }

  /* Print the address that the DLL was loaded at. */
  printf("Library is loaded at address %p.\n", hAddLib);

  /* Attempt to get the memory address of the "Add()" function. */
  if (! (Add = (AddFunc) GetProcAddress(hAddLib, "Add")))
  {
    fprintf(stderr, "Error locating \"Add\" function.\n");
    return EXIT_FAILURE;
  }

  /* Print the address of the "Add()" function. */
  printf("Add function is located at address %p.\n", Add);

  /* Call the function and display the results. */
  printf("7 + 41 = %d\n", Add(7, 41));

  /* Unload the DLL. */
  FreeLibrary(hAddLib);

  return EXIT_SUCCESS;
}

There are a few things here which should be noted. Firstly, the “LoadLibrary” and “GetProcAddress” functions return a NULL pointer on failure, and you should always check for a failure in either of these function calls. Secondly, the “LoadLibrary” function has both ANSI and Unicode versions, whereas “GetProcAddress” always takes an ANSI string for the function name—don’t let this catch you out in Unicode builds. Thirdly, we are using a C “typedef” to declare a function pointer type matching the signature of the function exported from the DLL. This signature should match exactly, including the calling convention, otherwise you can corrupt the stack when calling the function. Finally, the “FreeLibrary” function should be called when you are finished using functions exported from the DLL. The “FreeLibrary” function decrements the usage count of the DLL, and additionally unmaps the DLL from the process’s address space if the usage count reaches zero.

Running the application should result in an output such as the following:

z:\Users\mpayne\Documents\MinGWDLL>DynamicLoad.exe
Library is loaded at address 6DA40000.
Add function is located at address 6DA41280.
7 + 41 = 48

Of course in a real example you would not hard code the name of the DLL into the application. Instead, you would probably expose a user interface within the application which allows managing of plugins—exactly how this is done is left as an exercise for the reader.