
Contents
The idea for this article emerged from a failed attempt at solving a problem using an embedded Firebird database with ASP.NET. There is a great article on CodeProject on Embedded Firebird by Dan Letecky, but it is not a prerequisite for this article.
The Firebird ADO.NET Provider communicates with Firebird native DLL's. While using the embedded version, it communicates through API with 'fbembed.dll' which should be located in the bin directory of your application. ASP.NET web application assemblies, when run, get transferred to the "temporary ASP.NET files" directory. For instance:
'c:\windows\microsoft.net\framework\v1.1.4322\temporary asp.net files\webapplication1\946ca3d2\5cefcc750'
The 'fbembed.dll' DLL isn't a .NET assembly; it's a native DLL and therefore isn't copied to that directory. So after doing exhaustive Googling I came upon a code sample by Mattias Sjögren (Dynamic PInvoke method calls), which describes a method for building dynamic assemblies that contain API methods and changing their DLL paths. This article is the basis for my article.
The idea was to change the source of Firebird Provider so that all the hard coded references of 'fbembed.dll' are changed during runtime to point to the correct location of the DLL. I tried to get the correct path of the web application DLL by using Assembly.GetCallingAssembly().Location
, thinking that the calling assembly inside the provider would be the web application assembly. That call would return the path to the web application assembly where I could copy 'fbembed.dll' during runtime. This turned out to be very strange because that location pointed to the Firebird Provider assembly. For some reason the provider called itself! So I gave up that approach.
I am trying to give you a possible solution to the problem of using native DLL's whose path is not hard coded in the application. Sometimes it is not possible to put your own DLL's into the system32 directory, and sometimes it is not possible to use them even if they are in your bin directory (e.g. ASP.NET).
Instead of using the Firebird Provider source code, I made a simple class that holds a few API's (MessageBox
and MessageBoxEx
).
This approach consists of three parts.
- The original API class (hard coded DLL paths).
- The converter method.
- The surrogate class that other parts of your project call instead of the original API functions.
The converter method uses the original class as a template and makes a dynamic assembly that has API functions pointing to any path of your choice. Then the surrogate communicates with the dynamic class.
You may be asking yourself, will I have to ever change the calling method in the project to be able to call methods in the surrogate class. The answer is no, you won't. The surrogate will be using delegates with the same name, footprint, etc. and will have the same name as your original class. Just wait and see...
This is just a temporary solution before .NET 2.0 is released. It has some pretty cool methods that can make a delegate from a function pointer obtained by the LoadLibrary
API.
public class SimpleApiOriginal
{
[DllImport("user32.dll")]
public static extern int MessageBoxEx(IntPtr hWnd,
string lpText, string lpCaption,
uint uType, ushort wLanguageId);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd,
String text, String caption,
uint type);
}
This class serves as a template for the converter. The API's should be declared as public
static
. The reason for that will be explained later. Notice that all the API's refer to "user32.dll", this is important because the converter method assigns the same DLL path to all the functions. This could be improved in the converter, or you could create separate template classes for different API DLL's.
The dynamic class has the same API functions, however the DLL path is changed.
public class SimpleApiDynamic
{
[DllImport("c:\\Windows\\system32\\user32.dll")]
public static extern int MessageBoxEx(IntPtr hWnd,
string lpText, string lpCaption,
uint uType, ushort wLanguageId);
[DllImport("c:\\Windows\\system32\\user32.dll",
CharSet=CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd,
String text, String caption, uint type);
}
I changed the path to point to the user32.dll in 'c:\Windows\system32' directory. This was, of course, totally unnecessary because the original functions already pointed to the same DLL, but it just goes to show that you could change it to whatever you like.
Instead of calling the API's in the original class, you would call delegates (yes delegates; I love .NET!) in the surrogate class the same way you would call the original API functions. No difference. Those delegates on the other hand call the API's in the dynamic class. This surrogate class would have a static constructor (a type constructor) that builds the dynamic class and binds the delegates to the methods in the dynamic class before calling them.
public class SimpleApi
{
public delegate int MessageBoxExDelegate(IntPtr hWnd,
string lpText, string lpCaption,
uint uType, ushort wLanguageId);
public delegate int MessageBoxDelegate(IntPtr hWnd,
String text, String caption, uint type);
public static MessageBoxExDelegate MessageBoxEx;
public static MessageBoxDelegate MessageBox;
private static Type _dynamicType;
static SimpleApi()
{
_dynamicType =
CreateDynamicType(typeof(SimpleApiOriginal),
"SimpleApiDynamic")
if (_dynamicType == null)
throw new Exception();
MessageBox = Delegate.CreateDelegate(
typeof(MessageBoxDelegate),
_dynamicType.GetMethod("MessageBox")) as
MessageBoxDelegate;
MessageBoxEx = Delegate.CreateDelegate(
typeof(MessageBoxExDelegate),
_dynamicType.GetMethod("MessageBoxEx")) as
MessageBoxExDelegate;
}
private static Type CreateDynamicType(Type originalType,
string dynamicBaseName)
{
...
}
}
Calling the delegated methods of this class is quite simple:
SimpleApi.MessageBox(new IntPtr(0),
"New message text", "MessageBox caption!", 0);
Notice that it is absolutely the same as calling the API's in the original. The only thing you have to change in your project is to make a new class that holds all the delegates (the surrogate class) with the same name as your original class and rename your original class to something else or move it to another namespace.
The converter method is using reflection to retrieve method descriptions in the original class and their parameter descriptions. Based on that data it uses a slightly modified version of Mattias' algorithm to build the dynamic type.
We will go over the code in the CreateDynamicType
method step by step:
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = dynamicBaseName + "Assembly";
AssemblyBuilder assemblyBuilder =
AppDomain.CurrentDomain.DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.RunAndSave);
ModuleBuilder moduleBuilder =
assemblyBuilder.DefineDynamicModule(
dynamicBaseName + "Module",
dynamicBaseName + ".dll");
TypeBuilder typeBuilder =
moduleBuilder.DefineType(dynamicBaseName + "Type",
TypeAttributes.Class);
So far, we just created a dynamic assembly with a module that contains an empty type. Next we have to create the API functions with modified paths. We do that by iterating through the public static
methods of the original API class (originalType
):
MethodInfo[] minfos =
originalType.GetMethods(
BindingFlags.Public | BindingFlags.Static);
for (int i = 0; i < methodInfos.GetLength(0); i++)
{
...
}
Inside the loop we collect the method's parameters and save their types and attributes:
MethodInfo mi = methodInfos[i];
ParameterInfo[] methodParameters = mi.GetParameters();
int parameterCount = methodParameters.GetLength(0);
Type[] parameterTypes = new Type[parameterCount];
ParameterAttributes[] parameterAttributes =
new ParameterAttributes[parameterCount];
for (int j = 0; j < parameterCount; j++)
{
parameterTypes[j] = methodParameters[j].ParameterType;
parameterAttributes[j] = methodParameters[j].Attributes;
}
Next we create a MethodBuilder
that allows us to add a PInvoke
method in the dynamic type based on the original. The second parameter of the DefinePInvokeMethod
method is the path to the DLL. I'm using the function GetDynamicDllPath
that returns a string containing the path to the DLL.
MethodBuilder methodBuilder =
typeBuilder.DefinePInvokeMethod(
mi.Name,
GetDynamicDllPath(),
mi.Attributes,
mi.CallingConvention,
mi.ReturnType,
parameterTypes,
CallingConvention.StdCall,
CharSet.Auto);
for (int j = 0; j < parameterCount; j++)
methodBuilder.DefineParameter(j + 1,
parameterAttributes[j],
methodParameters[j].Name);
methodBuilder.SetImplementationFlags(
mi.GetMethodImplementationFlags());
This is the end of the loop and there are just a few more lines before we complete the converter.
Type retval = typeBuilder.CreateType();
assemblyBuilder.Save(dynamicBaseName + ".dll");
return retval;
Once more I will roughly explain the whole process.
- Rename your template API class to something else.
- Create a surrogate class with the same name as the original template class.
- Create delegates with the same footprint as the original API functions.
- Copy the converter method in your surrogate class along with the type constructor.
The hard work lies in writing the delegates and their respective variables. The converter only parses the API functions. API types and constants should be transferred to the surrogate class!
Here are some improvements that I could think of:
- Instead of using a template class, one could use a XML (or database) to store all the method info.
- Make the converter read the hard coded DLL path and decide based on that what to do next.
- Make the converter sensitive to public/private/internal and other parameters.
- ...
The converter also makes the dynamic API functions use CallingConvention.StdCall
, but not all APIs use that calling convention. That could also be improved...
- 14.8.2005 - The first version of the article.