C# – dynamic loading of a C DLL at run-time

The Problem

I am working on a C# project which needs to load one of a set of DLLs at run-time. Indeed, it needs to be able to load and unload them, possibly then loading a different one. The problem is similar to that of supporting plugins.

The DLLs are supplied by another company, so I have no control over them. They are written in C and export a set of C functions.

A Solution

Surprisingly, after a long search I discovered that C# does not seem to support dynamic linking. So in order to solve this problem, I need to use the dynamic linking functions supported by the WIN32 API, a set of low-level C functions.

A solution was suggested by an article by Jonathan Swift, which I was then able to adapt to meet my requirements.

The first step then was to create bindings to the WIN32 functions so that they are linked into the application. This is done using the Platform Invoke services provided in the namespace System.Runtime.InteropServices, and documented on MSDN.

using System;
using System.Runtime.InteropServices;

static class win32
{

    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string dllToLoad);

    [DllImport("kernel32.dll")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

    [DllImport("kernel32.dll")]
    public static extern bool FreeLibrary(IntPtr hModule);

}

The DllImport attribute tells the linker which external library to load the functions from.

When mapping from C# to C, it is necessary to “Marshall” the parameters to map C# types onto C types. However, these functions work as they stand since the default marshalling of the parameters is correct. I will revisit the marshalling issue in another article.

These functions perform the following tasks:

LoadLibrary
Loads the DLL into memory and returns a raw address of the DLL’s handle. If the DLL cannot be found, returns IntPtr.Zero.
GetProcAddress
Loads a function by name from the DLL. Returns a raw address of the function. If the function cannot be found, returns IntPtr.Zero.
FreeLibrary
Releases the DLL loaded by the LoadLibrary function.

The LoadLibrary and FreeLibrary functions can now be used directly in C#. However, the GetProcAddress function returns a function pointer and these cannot be called directly from C#. The trick is to convert the function pointer into a delegate using the GetDelegateForFunctionPointer method from the System.Runtime.InteropServices.Marshall class. The code for loading a function and converting it into a delegate is:

using System;
using System.Runtime.InteropServices;
...
IntPtr address = win32.GetProcAddress(m_dll, name);
System.Delegate fn_ptr = Marshal.GetDelegateForFunctionPointer(address, typeof(T));

In this case, the type T is the type of the delegate to convert to. Sadly, the GetDelegateForFunctionPointer method returns a System.Delegate rather than a T. However, this can now be wrapped into a generic method that does return the right type:

class plugins
{
    public T load_function<T>(string name) where T : class
    {
        IntPtr address = win32.GetProcAddress(m_dll, name);
        System.Delegate fn_ptr = Marshal.GetDelegateForFunctionPointer(address, typeof(T));
        return fn_ptr as T;
    }

}

The last line upcasts the System.Delegate onto type T. Of course, the real version has some error checking – for example you could throw an exception if the function wasn’t found.

So what is type T? This is a delegate type declaration for the C function. For example:

[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int Ready_t();

The attribute tells the compiler that this is an unmanaged function (no garbage collection etc.) and that it uses Windows calling conventions. In this case, the DLL providers have told me in their documentation that their DLLs use Winapi calling conventions.

Note that I have used the delegate keyword to indicate that this is a delegate type. I have used the name Ready_t to indicate that this is the type declaration of the function and not the actual function.

Again, in this case, the return type int already marshals correctly using the default conversions so there are no marshalling attributes on the function parameters.

The final stage to get a callable C# function is to use this delegate type as the T parameter of the generic load_function method:

Ready_t Ready = load_function<Ready_t>("Ready");
int ready_status = Ready();

The first line loads a function called “Ready” of type Ready_t. The second calls the function as if it were a C# method.

Leave a Reply