|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
ContentsIntroductionThe 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. BackgroundThe 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 The purpose of this articleI 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). Concept and codeInstead of using the Firebird Provider source code, I made a simple class that holds a few API's ( This approach consists of three parts.
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 The original API class - The template/// <summary>
/// Used as a API class "template"
/// </summary>
public class SimpleApiOriginal
{
// All api sould be declared static
// public because that is how
// my code detects api methods from the rest
[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 The dynamic API class - The generated classThe 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. The surrogate classInstead 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
{
// notice the same function parameters and return
// types here and in the original API class
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);
// the local delegate variables are named
// exactly like the original API functions
// calling them is easy:
// SimpleApi.MessageBox(something,
// "Testing", "Caption", 0);
//
public static MessageBoxExDelegate MessageBoxEx;
public static MessageBoxDelegate MessageBox;
// holds the dynamicly created type
private static Type _dynamicType;
/// <summary>
/// Initializes the SimpleApi type by
/// creating SimpleApiDynamicType and
/// binding the delegates to it
/// </summary>
static SimpleApi()
{
// CreateDynamicType is the method
// that builds our dynamic type
_dynamicType =
CreateDynamicType(typeof(SimpleApiOriginal),
"SimpleApiDynamic")
if (_dynamicType == null)
throw new Exception();
// bind a delegate on a type method
// this works if the original (and
// of course dynamic) api is static
MessageBox = Delegate.CreateDelegate(
typeof(MessageBoxDelegate),
_dynamicType.GetMethod("MessageBox")) as
MessageBoxDelegate;
MessageBoxEx = Delegate.CreateDelegate(
typeof(MessageBoxExDelegate),
_dynamicType.GetMethod("MessageBoxEx")) as
MessageBoxExDelegate;
}
/// <summary>
/// Creates the dynamic type.
/// </summary>
/// <param name="originalType">Type of
/// the original API class</param>
/// <param name="dynamicBaseName">Base name
/// of the dynamic base. (eg. SimpleApiDynamic"</param>
/// <returns></returns>
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 magicThe 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 // Create dynamic assembly
AssemblyName assemblyName = new AssemblyName();
// nothing fancy, "...Asembly", could be anything
assemblyName.Name = dynamicBaseName + "Assembly";
// The AssemblyBuilderAccess.RunAndSave attribute
// allows me to save this assembly later
// on so I can dissasemle and check it. In your release
// version it will be sufficient to use
// AssemblyBuilderAccess.Run because there
// is no need to save it.
AssemblyBuilder assemblyBuilder =
AppDomain.CurrentDomain.DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.RunAndSave);
// Add module to assembly
// I'm using an overloaded constructor
// here that creates a persistent module
// which can be saved to the disk
ModuleBuilder moduleBuilder =
assemblyBuilder.DefineDynamicModule(
dynamicBaseName + "Module",
dynamicBaseName + ".dll");
// Add a type to the module
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 // retrieve all the methods that are public
// and static in the originalType
MethodInfo[] minfos =
originalType.GetMethods(
BindingFlags.Public | BindingFlags.Static);
// loop through those methods
for (int i = 0; i < methodInfos.GetLength(0); i++)
{
...
}
Inside the loop we collect the method's parameters and save their types and attributes: // mi holds the info for an api method
MethodInfo mi = methodInfos[i];
// get all method parameters so we can use
// thier types and attributes later on
ParameterInfo[] methodParameters = mi.GetParameters();
int parameterCount = methodParameters.GetLength(0);
// variables to store parameter types and attributes
Type[] parameterTypes = new Type[parameterCount];
ParameterAttributes[] parameterAttributes =
new ParameterAttributes[parameterCount];
// save method parameter types and attributes
for (int j = 0; j < parameterCount; j++)
{
parameterTypes[j] = methodParameters[j].ParameterType;
parameterAttributes[j] = methodParameters[j].Attributes;
}
Next we create a //create a MethodBuilder for a PInvoke method
MethodBuilder methodBuilder =
typeBuilder.DefinePInvokeMethod(
mi.Name, // use same name as API function
GetDynamicDllPath(), // here we change the
// dynamic path of the dll's
mi.Attributes, // original method attributes
mi.CallingConvention, // original method
// calling convention
mi.ReturnType, // original method return type
parameterTypes, // the method parameter
// types we collected
CallingConvention.StdCall, // StdCall interop calling
// convention (possible problem)
CharSet.Auto);
// we have to additionally define
// the parameter Attributes and
// set them the same as the original
// parameter attributes
for (int j = 0; j < parameterCount; j++)
methodBuilder.DefineParameter(j + 1,
parameterAttributes[j],
methodParameters[j].Name);
// We set the implementation flags
// the same as the original method
methodBuilder.SetImplementationFlags(
mi.GetMethodImplementationFlags());
This is the end of the loop and there are just a few more lines before we complete the converter. // create the defined type
Type retval = typeBuilder.CreateType();
// save the dll in the bin directory,
// not necessary but informative.
// you can use Lutz Roeder's .NET Reflector to open it.
assemblyBuilder.Save(dynamicBaseName + ".dll");
// finally return the dynamic type!
return retval;
Using the method in your projectsOnce more I will roughly explain the whole process.
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! ImprovementsHere are some improvements that I could think of:
The converter also makes the dynamic API functions use History
|
||||||||||||||||||||||