Click here to Skip to main content
15,851,514 members
Articles / Programming Languages / C#

How to release an unmanaged library loaded into managed .NET code

Rate me:
Please Sign up or sign in to vote.
4.92/5 (14 votes)
4 Dec 2014CPOL1 min read 44.1K   25   12
Explains how to release an unmanaged library loaded into managed .NET code

Motivation

I had found this article on how to release a DLL library already loaded into a process using P-Invoke. It uses the 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 the 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 the same application domain again after unloading.

Solution

The solution is a 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 the following standard WinAPI functions for dynamical function loading:

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

We will use the following wrapping class:

C#
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 the imported functions - we will convert them into delegates (the following ones come from the sample project):

C#
// 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 implement our class calling the external DLL functionality with the IDisposable interface so it will automatically release the used DLL library when it goes out-of-scope or when it is finalized (in the example project are two functions which we will publish as Multiply() and Str2Int()).

C#
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 Delegate 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:

C#
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader NCR
Czech Republic Czech Republic
I'm software developer since 1996. I started with assembler on Intel 8051 CPUs, during years I was interested in C, C++, Sybase PowerBuilder, PHP, Sybase Anywhere Database, MSSQL server and multiplatform development.

Currently I'm developing in C++ and C# (this is my favorit and I spent some time with MCPD achievement). I'm also interested in design patterns.

Comments and Discussions

 
QuestionMy vote of 5, but Pin
Mark Kruger5-Dec-14 13:47
Mark Kruger5-Dec-14 13:47 
I looked to your solution and i liked the work, though if i would use many calls if would get bananas of the form it had. (i like solution which i don't need to copy). So i had like can i do something with it. I produced the next from it.

* unchanged*
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);
}

* new*
public class Dll :IDisposable
{
public static bool GetDll(string Name, out Dll FreshOne, ref int WhatWentWrong)
{
bool AllWentFine;
FreshOne = new Dll(Name, ref WhatWentWrong, out AllWentFine);
if (!AllWentFine)
{
FreshOne = null;
}

return AllWentFine;
}
private IntPtr dllPointer = IntPtr.Zero;
private bool IsDisposed = false;

private Dll(string Name, ref int WhatWentWrong, out bool AllWentFine)
{
try
{
dllPointer = UnsafeMethods.LoadLibrary(Name);
}
catch { }

if (dllPointer == IntPtr.Zero)
{
WhatWentWrong = Marshal.GetHRForLastWin32Error();
AllWentFine = false;
}
else
{
AllWentFine = true;
}
}

public bool GetFunction<t>(string functionName, out T Function, ref int WhatWentWrong)
where T : class
{
IntPtr functionPointer = IntPtr.Zero;

Function = null;
try
{
functionPointer = UnsafeMethods.GetProcAddress(dllPointer, functionName);
}
catch { }

if (functionPointer == IntPtr.Zero)
{
WhatWentWrong = Marshal.GetHRForLastWin32Error();

}
else
{
// Marshal to requested delegate
try
{
Function = Marshal.GetDelegateForFunctionPointer(functionPointer, typeof(T)) as T;
}
catch { }
}

return !object.ReferenceEquals(Function, null);
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

private void Dispose(bool Disposing)
{
if (!IsDisposed)
{
IsDisposed = true;

if (dllPointer != IntPtr.Zero)
{
//What u wanna do with an error on a dispose, free it again? ergo, skip the raise
try
{
UnsafeMethods.FreeLibrary(dllPointer);
}
catch{}

dllPointer = IntPtr.Zero;
}
}
}

~Dll()
{
Dispose(false);
}
}
*usage example
public partial class Form1 : Form
{
private delegate int MessageBox(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)]string lpText, [MarshalAs(UnmanagedType.LPWStr)]string Caption, uint uType);

public Form1()
{
InitializeComponent();
int WhatWentWrong = 0;
Dll User32;

if (Dll.GetDll("User32", out User32, ref WhatWentWrong))
{
MessageBox OurMessageBox;

if (User32.GetFunction<messagebox>("MessageBoxW", out OurMessageBox, ref WhatWentWrong))
{
OurMessageBox(this.Handle, "Howdy", "Just a caption", 0);
}

User32.Dispose();
}
}
}

* why this change*
This way u can easy-test (read bool, iow u can test it with an if) if dll loading went fine.
And from that part u can load any function if it's part of that dll.
And u can still dispose the dll the correct way.
With it the necessity to re-implement the code on each part u want to call dll's are gone.
Which makes it way easier to use, which i hope to have demonstrated.
Besides this your usage example with using just being 1 try finally u won't notice which part goes wrong, the dll loading, or which function call, it just traps out.
By returning the error of the loader by a reference i save the system from going through another error trap, which saves sever time.

Ergo by splitting of the loading part to the dll class u get a class which is only there if the loading of a dll has been proficient, and from that dll class u can load your functions by supplying your delegate with it's name and that's that. By supplying bools as return instead of the function the testing procedure get's shorter on the outside, which gives short code in loading a function call.

I placed the UnsafeMethods calls inside an blank try{}catch{} it should never happen those 3 don't do what they're supposed to do, but hell u don't want to confuse a user with something going wrong in those 3.

Your class which offers the api calls outside can ofcourse still be made the same from this example, with for example a boolean indicating it has been loaded correctly. For each function call offers u could check on being loaded well etc.

Note.
If u've any use of above code or pieces of it, feel free to use it or to use it on your page. A thank u or credits would be nice, nothing else needed it any way.

P.S. sorry for the not tabs present, the postmessage stripped and tabs and spaces away.

modified 6-Dec-14 10:00am.

AnswerRe: My vote of 5, but Pin
voloda26-Dec-14 8:24
voloda26-Dec-14 8:24 
GeneralRe: My vote of 5, but Pin
Mark Kruger6-Dec-14 8:31
Mark Kruger6-Dec-14 8:31 
GeneralRe: My vote of 5, but Pin
voloda26-Dec-14 11:48
voloda26-Dec-14 11:48 
QuestionGood article Pin
Ganesan Senthilvel19-Feb-12 15:55
Ganesan Senthilvel19-Feb-12 15:55 
AnswerRe: Good article Pin
voloda211-May-12 0:27
voloda211-May-12 0:27 
Suggestionreturn type of method LoadExternalFunctions Pin
Markus6414-Feb-12 1:56
Markus6414-Feb-12 1:56 
GeneralRe: return type of method LoadExternalFunctions Pin
voloda214-Feb-12 21:42
voloda214-Feb-12 21:42 
GeneralMy vote of 5 Pin
Markus6414-Feb-12 1:32
Markus6414-Feb-12 1:32 
SuggestionFreeLibrary issue Pin
Adnan Boz27-Sep-11 4:26
Adnan Boz27-Sep-11 4:26 
GeneralRe: FreeLibrary issue Pin
voloda227-Sep-11 7:53
voloda227-Sep-11 7:53 
GeneralRe: FreeLibrary issue Pin
Adnan Boz28-Sep-11 3:23
Adnan Boz28-Sep-11 3:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.