Introduction
Consider the situation where the native, Win32 DLL is unknown at compile time.
Perhaps its name or location is stored in the Windows Registry, or is selected by
the user from a FileOpen dialog. How can we call a function exported from this library, but
only resolved at runtime? The prescribed way to call native functions from the CLR is through
PInvoke, using the DllImport
attribute, but this must be declared at compile time or,
at the very least, generated on the fly using Reflection.Emit
. This article will
show an alternative way which requires the use of a little x86 assembler to meet our goal.
LoadLibrary
Windows provides two ways to load DLLs into the process of an executable. Either, the DLL
can be specified in the imports table and the Windows Loader will map the DLL automatically or the
LoadLibrary()
Win32 API call can be used. These are called implicit linking and explicit linking respectively. Both
these types can be seen in action using Dependency Walker available from
http://www.dependencywalker.com/. The CLR, Visual BASIC 6
and the /delayload
feature of MSVC6 all use explicit linking to call DLL functions.
We can quite happily call
LoadLibrary()
from C# to load a DLL into our address space. The problem comes when we try to call a function in the DLL.
Win32 provides the GetProcAddress()
function to return the memory address of a function exported from the given DLL and
we can easily obtain this memory address, but we can do nothing with it. It is simply an integer. The CLR provides
no way to jump to this location in memory, passing appropriate parameters too.
The CLR does allow us to do the reverse and pass a pointer to a managed function to a DLL using the delegate
keyword, but there
is no way to specify that a value returned from an unmanaged API call should be treated as a delegate.
Perhaps we may see this in .Net version 2, but for now we need to find another way to call the function.
Going low-level
One solution would be to write a small C++ DLL which merely forwards the call on. In other words, the C++ DLL
is acting as a proxy for our intended function. The downside is that a new C++ DLL would have to be created
every time a different DLL function needs to be called. The proxy function needs the exact number of parameters
that the real function takes. Every C# programmer needs to know C++ to be able to do this.
A much better solution is to write a small, reusable DLL in x86 assembly language which can forward function calls
to any location. This is trivial to write if we know a bit about how Win32 DLLs are called. All DLLs are called
using the stdcall
calling convention. This means that parameters are pushed onto the stack beginning at the right-most
parameter. Thus, the first in the parameter list will be at the top of the stack. The return address is then placed
on the stack and control is transferred to the callee. It is the callee's responsibility to pop all the parameters off
the stack and not fiddle with more registers than absolutely necessary.
Consider the following function declaration:
[DllImport("Invoke", CharSet=CharSet.Unicode)]
public extern static int InvokeFunc(int funcptr, int hwnd,
string message, string title, int flags);
It is implemented in a DLL called
Invoke.dll
and has the export name
InvokeFunc
.
It also takes five parameters, of which the last four are the exact parameters taken by the MessageBox() function.
The first parameter is an address of a function. We will leave the implementation of
InvokeFunc
for now and look
at code which can call this.
int hmod=LoadLibrary("User32");
int funcaddr=GetProcAddress(hmod, "MessageBoxW");
int result=InvokeFunc(funcaddr, 0, "Hello World",
".Net dynamic export invocation", 1 );
Console.WriteLine("Result of invocation is " + result);
FreeLibrary(hmod);
This code loads the DLL into our process space, finds the address of a function we wish to call, then uses our special
InvokeFunc()
function to call a function through a function pointer.
In the screenshot above, notice how GetProcAddress()
is being used to find the address of
GetProcAddress()
! This is because
PInvoke uses GetProcAddress
to find the address of any function specified by the
DllImport
attribute.
InvokeFunc Implementation
As we discussed earlier, the stdcall
calling conventions places parameters onto the stack in reverse order. Thus, our
function pointer will be at the top of the stack because it is first in the parameter list. If we can take
this parameter off the stack, then jump to that location in memory, it would be the equivalent of calling that function
without the intermediate proxy function.
The following fragment of x86 assembler achieves this
pop ecx ; save return address
pop edx ; Get function pointer
push ecx ; Restore return address
jmp edx ; Transfer control to the function pointer
Because we've used a jmp instruction rather than a call instruction, control will be
transferred directly
from the called function back to the CLR, passing any return value directly back.
Conclusion
Everything needed to compile and run this code is included with Visual Studio
.Net. The x86 DLL built can be
reused for calling any function pointer, not just a function with a specific signature and is only 2,560 bytes.
It turns out that if
you are not running .Net on Windows XP, there is a DLL with an equivalent proxy function to the one we built.
That DLL is msjava.dll
, which of course is missing from Windows XP due to the Microsoft-Sun
agreement on Java technology. msjava.dll
provides an export with the name call()
which duplicates this functionality.