How to release unmanaged library loaded into managed .NET code

· Read in about 3 min · (505 words) ·

Motivation

I had found this article on how to release DLL library already loaded into the process using P-Invoke. It uses LoadLibrary() and FreeLibrary() WINAPI calls to achieve this.

And what is wrong with it?

It forces to unload ALL instances of the DLL library currently loaded within process. Which means, that in the case you have more than one instance of the class using these external functions ALL these will stop working!

And that is not all - you cannot use this DLL in same application domain again after unloading.

Solution

Solution is pretty simple one, but I have to say that it wasn’t very obvious to me at the beginning. You can use P-Invoke to import following standard WinAPI functions for dynamical function loading:

  • LoadLibrary()
  • FreeLibrary()
  • GetProcAddress()

We will use following wrapping class:

internal static class UnsafeMethods
{
    [DllImport("kernel32.dll", SetLastError = true)]
    internal extern static IntPtr LoadLibrary(string libraryName);
    [DllImport("kernel32.dll", SetLastError = true)]
    internal extern static bool FreeLibrary(IntPtr hModule);
    [DllImport("kernel32.dll", SetLastError = true)]
    internal extern static IntPtr GetProcAddress(IntPtr hModule, string procName);
}

We also need signatures of imported functions - we will convert them into delegates (following ones come from sample project):

// int multiply(int value1, int value2);
private delegate int MultiplyDelegate(int value1, int value2);
// int str2int(const char *input);
private delegate int Str2IntDelegate([MarshalAs(UnmanagedType.LPStr)]string source);

Now we can create implement our class calling external DLL functionality with IDisposable interface so it will automatically release used DLL library when it will go out-of-scope or it will be finalized (in example project are two functions which we will publish as Multiply() and Str2Int()).

public class ExternalHelpers: IDisposable
{
    #region Private members
    private IntPtr _libraryHandle;
    private MultiplyDelegate _multiply;
    private Str2IntDelegate _str2Int;
    #endregion

    #region External functions delegates
    // int multiply(int value1, int value2);
    private delegate int MultiplyDelegate(int value1, int value2);
    // int str2int(const char *input);
    private delegate int Str2IntDelegate([MarshalAs(UnmanagedType.LPStr)]string source);
    #endregion

    public ExternalHelpers()
    {
        // dynamically load DLL using WinAPI
        _libraryHandle = UnsafeMethods.LoadLibrary(@"testing.dll");

        if (_libraryHandle == IntPtr.Zero)
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        // import functions as delegates using GetProcAddress
        _multiply = LoadExternalFunction<MultiplyDelegate>(@"multiply");
        _str2Int = LoadExternalFunction<Str2IntDelegate>(@"str2int");
    }

    public int Multiply(int value1, int value2)
    {
        // call method using delegate
        return _multiply(value1, value2);
    }

    public int Str2Int(string source)
    {
        // call method using delegate
        return _str2Int(source);
    }

    public void Dispose()
    {
        Dispose(true);

        GC.SuppressFinalize(this);
    }

    ~ExternalHelpers()
    {
        Dispose(false);
    }

    #region Private helper methods
    private T LoadExternalFunction<T>(string functionName)
        where T: class
    {
        Debug.Assert(!String.IsNullOrEmpty(functionName));
        // load function pointer
        IntPtr functionPointer = UnsafeMethods.GetProcAddress(_libraryHandle, functionName);

        if (functionPointer == IntPtr.Zero)
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        // Marshal to requested delegate
        return Marshal.GetDelegateForFunctionPointer(functionPointer, typeof(T)) as T;
    }

    private void Dispose(bool disposing)
    {
        if (disposing)
        {
            _multiply = null;
            _str2Int = null;
        }

        if (_libraryHandle != IntPtr.Zero)
        {
            if (!UnsafeMethods.FreeLibrary(_libraryHandle))
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

            _libraryHandle = IntPtr.Zero;
        }
    }
    #endregion
}

And finally - we can use it:

static void Main(string[] args)
{
    using(ExternalHelpers e = new ExternalHelpers())
    {
        const int value1 = 2;
        const int value2 = 3;
        const string strValue = "345";

        Console.WriteLine("{0} * {1} = {2}", value1, value2, e.Multiply(value1, value2));
        Console.WriteLine("{0} => {1}", strValue, e.Str2Int(strValue));
    }

    Console.ReadKey();
}

Looks easy? Yes it is :-)

Links