Dynamically invoking API dll functions in C# (well, sorta) - Everything from 0 to 1

Dynamically invoking API dll functions in C# (well, sorta)

K, this is a higher grade exercise, so if low level coding or windows API makes you queasy, then I suggest you have some Eno's and read this blog anyway J.

The problem
So, I'm going to assume you've dealt a little bit with windows or other API functions in C#. For the purpose of this article we're going to need an API function to use so I decided on the most simple and probably well known function (hopefully).

[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, string message, string caption, int type);

To use this function you would just call it like so:
System.Windows.Forms.Form form = new System.Windows.Forms.Form();
MessageBox(form.Handle, "Test", "test", 0);

Now say you need to dynamically invoke a Win32 function at runtime. IOW if you do not know which DLL or what the function name or entry point is at compile time and you want to be able to invoke any function at runtime (based on say a string containing the DLL path/name and a string containing the function name).

The Solution
OK, so now that we have the problem defined, here comes the tricky bit/s. Basically, there are Windows API functions we can implement to get and load the DLL, there is also functions to get the entry point of the API function in the DLL (from the name). The problem comes when you need to invoke the actual API function, but let's start with loading and unloading the DLL and getting the API function (procedure) address first.

[DllImport("kernel32", CharSet=CharSet.Ansi)]
private extern static IntPtr LoadLibrary(string libFileName);

[DllImport("kernel32")]
private extern static bool FreeLibrary(IntPtr hLibModule);

[DllImport("kernel32", CharSet=CharSet.Ansi)]
private extern static IntPtr GetProcAddress(IntPtr hLibModule, string procName);

Using the functions above should be pretty straight forward, but let's quickly run through how you would implement them.

IntPtr hMod = LoadLibrary("user32.dll");
IntPtr pAddr = GetProcAddress(hMod, "MessageBox");

// Invoke Function HERE! \\

FreeLibrary(hMod);

OK, so now we've loaded the user32 DLL and we've gotten the ProcAddress of the message box function. Also remember that we basically need the function to execute in the same process space that the managed application is running in. Basically to make this happen we need to jump to the exposed API function, the easiest (and quickest) way to do that is in a assembler win32 dll. Now basically we need to do the following:

  1. Get the function pointer (which would be passed as an argument).
  2. Jump to the function pointer (you want to jump not call because you want the arguments passed for the actual API function to persist and because you want the called API function to return its result to your managed code).

Here's the assembler function (this is Win32 ASM):

align DWORD

InvokeDLLFunction    proc stdcall public, fAddr:DWORD ;This is the function definition (We want the first argument to be a DWORD)
    pop    ecx ;Pop the return address (standard Win32ASM for dll function) off the stack 
    pop    edx ;Pop the argument off the stack 
    push   ecx ;Set the return address (standard Win32ASM for dll function) 
    jmp    edx ;Jump to the function address passed as the first argument
InvokeDLLFunction endp

Firstly (like with any return function in Win32ASM) you get the return address where this function will return (we pop it onto ECX and will push it back once we have our argument). Then you pop the function address (proc address) from the stack onto the ECX register which was passed as the first argument to this function (remember all the other arguments passed to this function will still be on the stack where the actual API function can still get them). Then we set the return address again (we want our called function to return there) by pushing it back onto the stack. Next we jump to the function address now stored on EDX. From there the intended function will execute and return to your managed code.

To use this function, we need to compile the full .ASM file (you need to include the standard win dll proc's like DllMain) to a windows DLL and define the function in C# with those defined above. Here's how you would call the compiled DLL.

[DllImport(@"c:\myprojectdir\bin\debug\InvokeFunction.dll", CharSet=CharSet.Ansi)]
private extern static IntPtr InvokeDLLFunction(IntPtr procAddrPtr, object[] arguments);

The C# code for the whole process:
IntPtr hMod = LoadLibrary("user32.dll");
IntPtr pAddr = GetProcAddress(hMod, "MessageBox");

System.Windows.Forms.Form form = new System.Windows.Forms.Form();
IntPtr pAddr = InvokeDLLFunction(pAddr, new object[]{form.Handle, "Test", "test", 0});

FreeLibrary(hMod);

I first saw this method implemented by Richard Birkby on CodeProject three or so years ago, and I've used it numerous times for various applications from then on. Below is a link to that original article which is still on CodeProject, if you are too lazy to write the full Win32 ASM DLL yourself you can use his one by downloading his full project:
http://www.codeproject.com/csharp/dyninvok.asp?df=100&forumid=2892&exp=0&select=867954

K, that's that. Go crazy…

Published Sunday, April 15, 2007 12:55 PM by weareu
Filed under: , ,

Comments

# re: Dynamically invoking API dll functions in C# (well, sorta)

Sunday, April 15, 2007 3:40 PM by Craig Nicholson

I dunno about you but I prefer this approach for .NET 2.0 (http://blogs.msdn.com/jonathanswift/archive/2006/10/03/Dynamically-calling-an-unmanaged-dll-from-.NET-_2800_C_23002900_.aspx). Its a lot cleaner and more maintainable.

# re: Dynamically invoking API dll functions in C# (well, sorta)

Sunday, April 15, 2007 4:39 PM by weareu

I agree, much cleaner. I did not know about the UnmanagedFunctionPointer attribute. Awesome, thanks. But I still prefer this method if you do not know the arguments or the function name. Allthough I reckon that you could probably also define a delegate that takes an object array as long as they are marshaled correctly (dunno, anyone tried it before?)...

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above: