Click here to Skip to main content
15,884,061 members
Articles / Programming Languages / C#
Article

DLLImport with dynamically bound native DLLs using Reflection

Rate me:
Please Sign up or sign in to vote.
4.85/5 (18 votes)
13 Aug 20056 min read 208.7K   1.9K   105   23
This article demonstrates how to make dynamic assemblies with API functions using DllImport attribute and using custom paths instead of hard coded ones.

Image 1

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

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

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

C#
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:

C#
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:

C#
// 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):

C#
// 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:

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

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

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


Written By
Web Developer
Croatia Croatia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMarshalling: Using native DLLs in .NET Pin
meukjeboer29-Aug-08 4:33
meukjeboer29-Aug-08 4:33 
Please read this blog post:
http://blog.rednael.com/2008/08/29/MarshallingUsingNativeDLLsInNET.aspx[^]

It shows which types are interoperable, how to import a DLL, how to pass strings and how to de-reference pointers.
Generalgive me a hint Pin
massimo_ negroni23-Apr-07 8:21
massimo_ negroni23-Apr-07 8:21 
GeneralError when call function in DLL Pin
thanhhhhhh7-Jul-06 19:30
thanhhhhhh7-Jul-06 19:30 
QuestionA problem with P/Invoke Pin
t4ure4n16-Apr-06 7:03
t4ure4n16-Apr-06 7:03 
AnswerRe: A problem with P/Invoke Pin
fkoestner15-Jun-06 8:17
fkoestner15-Jun-06 8:17 
Generalreleasing resources Pin
fatih isikhan5-Dec-05 3:11
fatih isikhan5-Dec-05 3:11 
AnswerRe: releasing resources Pin
Fran Pregernik8-Dec-05 20:45
Fran Pregernik8-Dec-05 20:45 
GeneralRe: releasing resources Pin
fatih isikhan9-Dec-05 0:21
fatih isikhan9-Dec-05 0:21 
GeneralCalling a DLL is there any method other than &quot;GetProcAddress&quot; Pin
amitcjoshi12-Oct-05 19:10
amitcjoshi12-Oct-05 19:10 
GeneralYuck Pin
vbrtsdfvdsf15-Aug-05 11:38
vbrtsdfvdsf15-Aug-05 11:38 
GeneralRe: Yuck Pin
kaptaintens15-Aug-05 18:26
kaptaintens15-Aug-05 18:26 
GeneralRe: Yuck Pin
Fran Pregernik15-Aug-05 20:12
Fran Pregernik15-Aug-05 20:12 
GeneralRe: Yuck Pin
Fran Pregernik16-Aug-05 23:18
Fran Pregernik16-Aug-05 23:18 
GeneralRe: Yuck Pin
Flier Lu18-Aug-05 3:28
Flier Lu18-Aug-05 3:28 
GeneralRe: Yuck Pin
valex12317-Aug-05 11:48
valex12317-Aug-05 11:48 
GeneralRe: Yuck Pin
Fran Pregernik17-Aug-05 20:16
Fran Pregernik17-Aug-05 20:16 
GeneralRe: Yuck Pin
Fran Pregernik18-Aug-05 19:43
Fran Pregernik18-Aug-05 19:43 
GeneralRe: Yuck Pin
Domenic1-Sep-05 7:28
Domenic1-Sep-05 7:28 
GeneralRe: Yuck Pin
Fran Pregernik2-Sep-05 1:01
Fran Pregernik2-Sep-05 1:01 
GeneralRe: Yuck Pin
Domenic2-Sep-05 2:43
Domenic2-Sep-05 2:43 
GeneralRe: Yuck Pin
Fran Pregernik2-Sep-05 2:54
Fran Pregernik2-Sep-05 2:54 

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

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