Dynamically calling an unmanaged dll from .NET (C#)


This sample is in response to a question left on my previous post, namely how to call an unmanaged dll from managed code when the dll in question isn’t known until runtime (for instance, the path is stored in the registry, or an xml file, etc etc).


Apologies if this sample seems a little hurried, but I have another presentation to write and so time is short!


So let’s begin.


To start and to refresh our memories, let’s create a very basic C++ dll that does very little….. your code should resemble the following (check out my previous post for more info on this):


Header file


extern “C” __declspec(dllexport) int MultiplyByTen(int numberToMultiply);


Source code file


#include “DynamicDLLToCall.h”


int MultiplyByTen(int numberToMultiply)
{
        int returnValue = numberToMultiply * 10;
        return returnValue;
}
 


As you can probably infer from the function name, an int is passed into this function and it will return the number passed in multiplied by ten. Told you it would be simple.


Now comes the more interesting part, actually calling this dll dynamically from your C# source code. There are two Win32 functions that are going to help us do this:


1) LoadLibrary – returns a handle to the dll in question
2) GetProcAddress – obtain the address of an exported function within the previously loaded dll


The rest is rather simple. We use LoadLibrary and GetProcAddress to get the address of the function within the dll we want to call, and then we use the GetDelegateForFunctionPointer static method within the Marshal class to assign this address to a C# delegate that we define. Take a look at the following C# code:


static class NativeMethods
{
        [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);
}


class Program
{
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        private delegate int MultiplyByTen(int numberToMultiply);

        static void Main(string[] args)
        {
                IntPtr pDll = NativeMethods.LoadLibrary(@”PathToYourDll.DLL”);
                //oh dear, error handling here
                //if (pDll == IntPtr.Zero)

                IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, “MultiplyByTen”);
                //oh dear, error handling here
                //if(pAddressOfFunctionToCall == IntPtr.Zero)


                MultiplyByTen multiplyByTen = (MultiplyByTen)Marshal.GetDelegateForFunctionPointer(
                                                                                        pAddressOfFunctionToCall,
                                                                                        typeof(MultiplyByTen));

                int theResult = multiplyByTen(10);


                bool result = NativeMethods.FreeLibrary(pDll);
                //remaining code here

                Console.WriteLine(theResult);
        }


The only item worthy of note is the UnmanagedFunctionPointer attribute, which was introduced to version 2.0 of the .NET framework, check out the docs online for more information.


Hope this helps.


Comments (38)

  1. Steve says:

    Great follow-up Jonathan. Could you speak to any performance issues related to this type of mechanism. (Marhsalling, some-type-of-reflection, etc.).

  2. blairio says:

    Thanks for posting a helpful response.  Much appreciated.

    PS. Is it safe to assume that at some point you should call the native function FreeLibrary or is that uneccessary?

    =B

  3. JonathanSwift says:

    Hi Steve and Blairio.

    First off, when explicitly calling LoadLibrary FreeLibrary should indeed be called. I’ll modify my sample to show this call. As well as this, I’m going to add another post that shows how to free memory that was allocated by unmanaged code that the runtime can’t clean up for you. It’s quite interesting.

    Steve, in terms of performance then whichever way you look at it interop is going to be a hit, no question. The whole topic of performance is very subjective and depends entirely on your targets. What’s slow to one application may well be quick to another. Importantly, how slow a particular interop call is seen to be depends on the rest of the request the call is – if database calls/IO etc are in the mix then I’ve no doubt the interop call will be a very small percentage of the whole, and so even if perceived as ‘slow’ won’t make much difference to the overall performance.

    If however you’re making an interop call in a tight loop in a request that only performs calculations, you could well feel the hit.

    If you have a particular scenario in mind please let me know, and I’ll decompose it on here for you and show the most performant way to attack it. There are techniques to use that can help matters.

    Thanks guys

  4. Steve says:

    Hello again Jonathan,

    Thanks for the followup. I didn’t really have a particular scenario in mind…just a feeling that there might be some gotchyas there. You always here that kind of thing when talking about reflection and I wasn’t sure if they were similar ideas (in the dynamic sense I mean).

    Like everything else though, it is a trade-off…and if you can use existing, robust functions maybe the "delay" is worthwhile.

    Anyways, thanks again. Looking forward to more.

  5. JonathanSwift says:

    No worries Steve. After I’ve put up a post or two showing how to clear unmanaged memory from managed code I’ll devote one or two to different aspects of interop performance, and looking at what types to use to increase performance and also refactoring for performance.

    Hopefully these will answer some or all of your questions.

    Cheers

  6. Bas & Joep says:

    Thanks Jonathan! This is exactly what we’ve been looking for…

  7. Anonymous says:

    I’m working with a native library that exports a good number of functions.  The problem is that I need to be able to unload the DLL, replace it, and then reload it.  I’ve noticed that apparently DllImport only does a LoadLibrary() and GetProcAddress() the first time you call an imported function.  So if you FreeLibrary() then LoadLibrary() it is possible it will be loaded into a different address space, so the old function pointers will no longer be valid and you will get access violations.

    Currently I’ve got a class something like this:

    class NativeWrapper

    {

      NativeWrapper(string filename)

      {

         m_strFileName = filename;

      }

      Load()

      {

         m_hModule = LoadLibrary(m_strFileName);

      }

      Free()

      {

         FreeLibrary(m_hModule);

         m_pfnSomeMethod = null;

      }

      int SomeMethod()

      {

         if(m_pfnSomeMethod == null)

         {

            //pseudocode; you get the idea

            m_pfnSomeMethod = (SomeMethodDelegate)GetProcAddress(m_hModule, "SomeMethod");

         }

         return m_pfnSomeMethod();

      }

    }

    I have like 30 functions implemented exactly the same way, and I need to do this for about 5 or 6 libraries I am using.  My question is this: is there any way to automate this?  I was thinking of implementing an attribute similar to DllImport, but I don’t know where to start or if that is even possible.

    The way I understand it, DllImport basically calls LoadLibrary() the first time any function in the library is called, and GetProcAddress() the first time each function is called, and caches the results.  Is DllImport a standard attribute, or does it have some kind of special consideration in the CLR?

  8. zawmn says:

    I declare functions for excel export like that

    <DllImport("ExcelFG.dll", EntryPoint:="SetCell")> _

           Shared Sub SetCell(ByVal nRow As Integer, ByVal nColumn As Integer, ByVal cellValue As Byte())

           End Sub

           <DllImport("ExcelFG.dll", EntryPoint:="WriteFile")> _

           Shared Sub WriteFile()

           End Sub

    And loading the dynamic dll path like that

    Dim DLLPath As String

                   DLLPath = Server.MapPath("bin") & "ExcelFG.dll"

                   Dim mlib As IntPtr = LoadLibrary(DLLPath)

                   If mlib = 0 Then

                       Response.Write("Load Library Fail" & Server.MapPath("bin") & "ExcelFG.dll")

                       Exit Sub

                   End If

    And free the loaded library with

    FreeLibrary(mlib)

    Everything is work fine for my asp application.  But after I call the function, ExcelFG.dll is access deny and can’t delete or overwrite.

  9. twig says:

    thank you so very much for posting this

    its a very elegant fix for this problem.

  10. vjm says:

    Gr8 work indeed. It has solved my problem.

    Thanx a lot.

  11. euph0ria says:

    very helpful article!!

    thank you very much!!

  12. alex says:

    add this to the top of your code to use DllImport:

    using System.Runtime.InteropServices;

  13. mingfei says:

    thank you very much

    but your .net framework is 2005,

    this ‘Marshal.GetDelegateForFunctionPointer’ function not provider by vs2003

    i want to ynamically call an unmanaged dll from .NET2003 (C#)

    you can help me

    thanks

    my eamil is "mingfei2007316@yahoo.com.cn"

  14. Help says:

    I have a FunctionInC (char* &dataPtr), how do I get the data from dataPtr?

    Thanks!

  15. Mark Collins says:

    Hello,

    Great article, thanks for the hardwork. Do you know how to use GetProcAddress with entry point numbers instead of names? I have a .def file containing entry points with noname and only numbers. This page….  http://msdn.microsoft.com/en-us/library/ms683212(VS.85).aspx

    Says that is it possible using a "low-order word". I have no idea what in c# would come close to a low order word. Any help you can offer would be greatly appreciated..

  16. Sabina says:

    Hi,

    Suppose I only know the signature of the native function I am calling at runtime (so I can’t explicitly set up the appropriate delegate), is there a way to call the native function?

    Any help is appreciated!

  17. Thanks for a handy piece of code.  Just what I wanted.

  18. Brad Figler says:

    Ahhh, just what I was looking for!!

    I was running in to a 126 error when trying to run load my legacy DLL and it turned out that it was because of the dependencies of the DLL itself. The dependencies where loaded in the same directory as the DLL I was trying to load but apparently I need them in the %PATH% in order for it to load properly. I fixed the issue by doing this:

    string path = Environment.GetEnvironmentVariable( "PATH" );

    path += ";"+"My Path"+"\";

    Environment.SetEnvironmentVariable( "PATH", path );

    This sets the environment variable for the process while it is running and has no effect on the system itself.

    Hope that saves someone pulling hair out as much hair as I did.

  19. Taher Hassan says:

    I have a question regarding calling C++ from C#. What about variable types compatibility. In case of int, float, double, and bool, It is good. But, I have one C++ function for printing in a file that uses ostream. When I use it in C# I tried to use streamwriter but it did not work it gives me violation exception. Any tips

    Thanks

  20. Nick says:

    Brilliant tutorial Jonathan.

    I’d been trying to work out how to do this for ages.

    I found it was very important to use dumpbin to find out what the real function names of the dll are as the c compiler had prefixed "_" and postfixed "@0" to all my functions and until I discovered this my code didn’t work.

  21. Yp says:

    When I call FreeLibrary with correct handle of the loaded library, program hangs !!!

    What is the reason ?

  22. Bernard says:

    I’m also having problems when calling FreeLibrary, the app hangs and when I’m in debug I get "memory couldn’t be read at 0x00000" or something like that.

    found this which might indicate the problem

    http://blogs.msdn.com/jmstall/archive/2007/01/06/Typesafe-GetProcAddress.aspx

  23. Afein says:

    @ Brad Figler, thanks for your solution to error 126 i definitely was pulling my hair out!

  24. Dani says:

    Thank you very much for doing this. I don’t have to deal with unmanaged/cli C++ anymore.

  25. Roopesh says:

    Hi,

    Iam trying to execute the code, that you have provided, it is throwing error message: "An attempt was made to the function with incorrect Format HRESULT:0x80070002B

    Environment:

    Microsoft Visual Studio 2008

  26. Se7en Soft says:

    Thank you very much for this code snippet. Because of it I have been able to successfully compile my application for any cpu and dynamically load x86/x64 native assemblies as required by the running platform.

    Best Regards,

    Se7en Soft

  27. Chad says:

    Great piece of code!

    Is there a way to tell your C# project to include the file.. pull it into the bin directory even though you are doing the late binding?  

    I am trying to reference a third party app and want to make sure that it gets pulled to the bin to make the the deployment easier.

    Any ideas on how to do this?

  28. mopdep says:

    using System;

    using System.Collections.Generic;

    using System.ComponentModel;

    using System.Data;

    using System.Drawing;

    using System.Text;

    using System.Windows.Forms;

    using System.Reflection;

    using System.IO;

    using System.Runtime.InteropServices;

    using System.Threading;

    namespace Plug_Load

    {

       public partial class Form1 : Form

       {

           #region P/Invoke

           [DllImport("kernel32.dll")]

           static extern IntPtr LoadLibrary(string lpFileName);

           [DllImport("kernel32.dll")]

    public static extern bool FreeLibrary(IntPtr hModule);

           [DllImport("kernel32.dll")]

           public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

           [DllImport("kernel32.dll")]

           static extern IntPtr GetModuleHandle(string lpFileName);

           [DllImport("kernel32.dll")]

           static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags);

           [DllImport("user32.dll", EntryPoint = "LoadMenu")]

           public static extern int LoadMenu(int hInstance, string lpString);

           [DllImport("user32.dll", EntryPoint = "LoadMenuIndirect")]

           public static extern int LoadMenuIndirectA(int lpMenuTemplate);

           [DllImport("user32.dll")]

           static extern int LoadString(IntPtr hInstance, int uID, StringBuilder lpBuffer, int nBufferMax);

           [DllImport("user32.dll")]

           public static extern IntPtr LoadImage(IntPtr hInstance, int uID, uint type, int width, int height, int load);

           [DllImport("user32.dll")]

           public static extern IntPtr LoadBitmap(IntPtr hInstance, int uID);

           [DllImport("user32.dll")]

           public static extern IntPtr LoadIcon(IntPtr hInstance, int uID);

           [DllImport("kernel32.dll")]

           public static extern IntPtr FindResource(IntPtr hModule, int lpID, string lpType);

           [DllImport("kernel32.dll", SetLastError = true)]

           public static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);

           [DllImport("kernel32.dll", SetLastError = true)]

           public static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);

           [DllImport("user32.dll")]

           public static extern IntPtr LoadMenu(IntPtr hInstance, int uID);

           private Thread t = null;      

           public string pathe = Directory.CreateDirectory("Plugins").Name;

           public string[] files = Directory.GetFiles("Plugins", "*.dll");

           public const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002;

           #endregion

           public Form1()

           {

               InitializeComponent();

           }

           private void button1_Click(object sender, EventArgs e)

           {

               //windows exit

               Close();

           }

           private void Form1_Load(object sender, EventArgs e)

           {

               t = new Thread(new ThreadStart(Plugin_loader));

               t.Start();

           }

           private void Plugin_loader()

           {

               foreach (string file in files)

               {

                   FileInfo fr = new FileInfo(file);

                   IntPtr hMod = LoadLibraryEx(fr.FullName, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE);

                   Encoding.Default.GetString(BitConverter.GetBytes(36));

                   IntPtr hRes = FindResource(hMod, 36, "Bitmap");

                   uint size = SizeofResource(hMod, hRes);

                   IntPtr pt = LoadResource(hMod, hRes);

                   FreeLibrary(hMod);

                   Bitmap bmp;

                   byte[] bPtr = new byte[size];

                   Marshal.Copy(pt, bPtr, 0, (int)size);

                   using (MemoryStream m = new MemoryStream(bPtr))

                       bmp = (Bitmap)Bitmap.FromStream(m);

                   menuStrip1.Items.Add("Image", bmp);

               }

               t.Abort();

           }

           private void button2_Click(object sender, EventArgs e)

           {

           }

       }

    }

  29. Jas says:

    When I compile the app, I get the following error

    ‘NativeMathWrapper.NativeMath.NativeMethods’ does not contain a definition for ‘LoadLibrary’

    I havea  using System.Runtime.InteropServices;

    Also I am using VS 2010 RC.

    Any idea what the problem coule be?

    thanks

    Jas

  30. Jas says:

    Oops, I meant

    ‘System.Runtime.InteropServices.NativeMethods’ is inaccessible due to its protection level

  31. Martin Costello says:

    It is worth noting that via information from these two pages:

    msdn.microsoft.com/…/ff361650.aspx

    msdn.microsoft.com/…/0htdy0k3.aspx

    I've discovered that if you get the calling convention on the function pointer wrong, in .NET 4.0 the process will exit with no error other than a process exit code of 0xc0000409.  .NET 3.5 and previous versions of the Framework would clean up for you if you got it wrong, but 4.0 doesn't.

    I suggest using [UnmanagedFunctionPointer(CallingConvention.StdCall)] unless you definitely know that's not what you need, or it can lead to a nasty gotcha and many hours of debugging…

  32. Dave says:

    Thanks Jonathan for a an excellent blog, this is exactly what I want to do :o)

  33. Dan says:

    I know this is an old blog but I'm having an issue that I thought I would throw out there to see if anyone has seen this.

    I am successful at loading my C++ DLL and calling the methods within that DLL, but the issue I'm having is that I am trying to store an instance of the delegate in my class so that I can call it later on in my program when needed. So in other words, I load everything up on my form load and then I want to call my delegate method when the user clicks a button. At the time of the button click though, my delegate is null so it crashes if I try to call it. I have verified that it does indeed work if I call the method right after my call to GetProcAddress. Has anyone seen this?

    TIA

  34. Dan says:

    Ok – disregard my previous post. It was a stupid user error. "Stupid User" being the key words. 😉  I was refactoring my code and did not remove the declaration of my objects from within the method so I had a locally scoped object named the same as my class object and this was causing my problem.

  35. AnandKumar says:

    Thanks for you efforts. It helped me a lot.

  36. It won't compile if I use:

    extern "C" __declspec(dllexport) int MultiplyByTen(int numberToMultiply);

    but it will compile if I use:

    extern "C" __declspec(dllexport) int MultiplyByTen(int);

    VS2010, standard MS compiler. It took me a while to figure that out via other (newer) articles so it might be worth noting because these are probably some changes for newer versions of compilers or VS, and might be a typical problem if people upgrade (or maybe I'm the only one).

  37. MartinDodd says:

    Could someone tell me what is going wrong with this code:

    I am trying to talk to the following C++ code function:

    void SortStructDescF(long pStruct, int numStructures, short structSize, short numSortValues )

    {

    mSortValues = numSortValues;

    qsort((void*)pStruct, numStructures, structSize, CompareFloatsDesc);

    }

    in a  .NET application.  I am using the following declarations for the C++ function in the .NET application:

    <DllImport("OptiDll.dll",EntryPoint:="SortStructDescF")> _

    Public Shared Function SortStructDescF(ByRef pStruct0 As Object, ByVal numStructures As Integer, ByVal structSize As Short, ByVal outstrmax As Short) As Integer

    End Function

    When I try and call the function SortStructDescF in the .NET application using:

    SortStructDescF(mLeftPoints(0), pointCount, CShort(LenB(mLeftPoints(0))), CShort(1))SortStructDescF(mLeftPoints(0), pointCount, CShort(LenB(mLeftPoints(0))), CShort(1))

    The application exits with the error:

    MyApplication.exe: Managed' has exited with code -1073740791 (0xc0000409).

  38. Could someone tell me what is going wrong with this code:

    I am trying to talk to the following C++ code function:

    void SortStructDescF(long pStruct, int numStructures, short structSize, short numSortValues )

    {

    mSortValues = numSortValues;

    qsort((void*)pStruct, numStructures, structSize, CompareFloatsDesc);

    }

    in a  .NET application.  I am using the following declarations for the C++ function in the .NET application:

    <DllImport("OptiDll.dll",EntryPoint:="SortStructDescF")> _

    Public Shared Function SortStructDescF(ByRef pStruct0 As Object, ByVal numStructures As Integer, ByVal structSize As Short, ByVal outstrmax As Short) As Integer

    End Function

    When I try and call the function SortStructDescF in the .NET application using:

    SortStructDescF(mLeftPoints(0), pointCount, CShort(LenB(mLeftPoints(0))), CShort(1))SortStructDescF(mLeftPoints(0), pointCount, CShort(LenB(mLeftPoints(0))), CShort(1))

    The application exits with the error:

    MyApplication.exe: Managed' has exited with code -1073740791 (0xc0000409).