Udostępnij za pośrednictwem


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

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

  • Anonymous
    October 06, 2006
    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

  • Anonymous
    October 09, 2006
    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

  • Anonymous
    October 10, 2006
    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.

  • Anonymous
    October 10, 2006
    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

  • Anonymous
    October 17, 2006
    Thanks Jonathan! This is exactly what we've been looking for...

  • Anonymous
    October 30, 2006
    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?

  • Anonymous
    March 30, 2007
    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.

  • Anonymous
    July 25, 2007
    thank you so very much for posting this its a very elegant fix for this problem.

  • Anonymous
    July 29, 2007
    Gr8 work indeed. It has solved my problem. Thanx a lot.

  • Anonymous
    October 15, 2007
    very helpful article!! thank you very much!!

  • Anonymous
    December 11, 2007
    add this to the top of your code to use DllImport: using System.Runtime.InteropServices;

  • Anonymous
    March 25, 2008
    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"

  • Anonymous
    April 02, 2008
    I have a FunctionInC (char* &dataPtr), how do I get the data from dataPtr? Thanks!

  • Anonymous
    May 08, 2008
    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..

  • Anonymous
    May 08, 2008
    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!

  • Anonymous
    August 27, 2008
    Thanks for a handy piece of code.  Just what I wanted.

  • Anonymous
    October 09, 2008
    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"+"&quot;; 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.

  • Anonymous
    January 01, 2009
    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

  • Anonymous
    January 13, 2009
    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.

  • Anonymous
    February 24, 2009
    When I call FreeLibrary with correct handle of the loaded library, program hangs !!! What is the reason ?

  • Anonymous
    March 04, 2009
    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

  • Anonymous
    April 20, 2009
    @ Brad Figler, thanks for your solution to error 126 i definitely was pulling my hair out!

  • Anonymous
    April 23, 2009
    Thank you very much for doing this. I don't have to deal with unmanaged/cli C++ anymore.

  • Anonymous
    May 22, 2009
    The comment has been removed

  • Anonymous
    November 25, 2009
    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

  • Anonymous
    December 11, 2009
    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?

  • Anonymous
    January 08, 2010
    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)        {        }    } }

  • Anonymous
    February 18, 2010
    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

  • Anonymous
    February 18, 2010
    Oops, I meant 'System.Runtime.InteropServices.NativeMethods' is inaccessible due to its protection level

  • Anonymous
    June 07, 2010
    The comment has been removed

  • Anonymous
    October 10, 2011
    Thanks Jonathan for a an excellent blog, this is exactly what I want to do :o)

  • Anonymous
    October 12, 2011
    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

  • Anonymous
    October 12, 2011
    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.

  • Anonymous
    July 29, 2012
    Thanks for you efforts. It helped me a lot.

  • Anonymous
    September 20, 2012
    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).

  • Anonymous
    November 11, 2012
    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).

  • Anonymous
    November 11, 2012
    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).