Click here to Skip to main content
Click here to Skip to main content

DLLImport with dynamically bound native DLLs using Reflection

, 13 Aug 2005
Rate this:
Please Sign up or sign in to vote.
This article demonstrates how to make dynamic assemblies with API functions using DllImport attribute and using custom paths instead of hard coded ones.

Contents

Introduction

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.

Background

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.

The purpose of this article

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).

Concept and code

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.

  1. The original API class (hard coded DLL paths).
  2. The converter method.
  3. 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.

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 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 API class - The generated class

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.

The surrogate class

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
{

    // 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 magic

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:

// 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 public static methods of the original API class (originalType):

// 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 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.

    //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 projects

Once more I will roughly explain the whole process.

  1. Rename your template API class to something else.
  2. Create a surrogate class with the same name as the original template class.
  3. Create delegates with the same footprint as the original API functions.
  4. 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!

Improvements

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...

History

  • 14.8.2005 - The first version of the article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Fran Pregernik
Web Developer
Croatia Croatia
No Biography provided

Comments and Discussions

 
GeneralMarshalling: Using native DLLs in .NET Pinmembermeukjeboer29-Aug-08 4:33 
Generalgive me a hint Pinmembermassimo_ negroni23-Apr-07 8:21 
Thanks for this,
I've wrote a faq and some hint on this and you can check on
 
www.marichitas.com/expert_view.aspx on section ASP.NET how to use
dllimport with ASP.NET and solve some problem.
 
Massimo
GeneralError when call function in DLL Pinmemberthanhhhhhh7-Jul-06 19:30 
QuestionA problem with P/Invoke Pinmembert4urean16-Apr-06 7:03 
AnswerRe: A problem with P/Invoke Pinmemberfkoestner15-Jun-06 8:17 
Generalreleasing resources Pinmemberfatih isikhan5-Dec-05 3:11 
AnswerRe: releasing resources PinmemberFran Pregernik8-Dec-05 20:45 
GeneralRe: releasing resources Pinmemberfatih isikhan9-Dec-05 0:21 
GeneralCalling a DLL is there any method other than &quot;GetProcAddress&quot; Pinmemberamitcjoshi12-Oct-05 19:10 
GeneralYuck Pinmembervbrtsdfvdsf15-Aug-05 11:38 
GeneralRe: Yuck Pinmemberdannyres15-Aug-05 18:26 
GeneralRe: Yuck PinmemberFran Pregernik15-Aug-05 20:12 
GeneralRe: Yuck PinmemberFran Pregernik16-Aug-05 23:18 
GeneralRe: Yuck PinmemberFlier Lu18-Aug-05 3:28 
GeneralRe: Yuck Pinmembervalex12317-Aug-05 11:48 
GeneralRe: Yuck PinmemberFran Pregernik17-Aug-05 20:16 
GeneralRe: Yuck PinmemberFran Pregernik18-Aug-05 19:43 
GeneralRe: Yuck PinmemberDomenic1-Sep-05 7:28 
GeneralRe: Yuck PinmemberFran Pregernik2-Sep-05 1:01 
GeneralRe: Yuck PinmemberDomenic2-Sep-05 2:43 
GeneralRe: Yuck PinmemberFran Pregernik2-Sep-05 2:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 14 Aug 2005
Article Copyright 2005 by Fran Pregernik
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid