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

Reflection.Emit based native library call

, 3 Apr 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Reflection.Emit based native library call
Introduction

In this article a solution to loading and unloading of native dll libraries selectively both in 32 bit and 64 bit platforms is proposed. The aim is to hide the complexity of loading and unloading of native library behind an object lifetime such that creation of an instance will load the library, library will stay in memory as long as needed and explicitly calling Dispose on the object will free native resources. Reliance on garbage collection is not appropriate here because a tight control on the native resource allocation is intended.

Background

Classical approach to use a native library in C# is to use DllImport. The problem with DllImport is that the dll stay loaded until the application exits. In order to remedy this, you can load the dll by yourself to get the module handle and then call FreeLibrary when you are done using it. However, such a use not only leads to so much extra code, it also does not appropriately deal with the bitness of the application. As we know that a C# application compiled with AnyCPU run in different modes depending on whether the platform is 64bit or 32bit. If 64bit platform is available, it will run as a 64bit process, otherwise fall back to run as 32bit process. This behavior limits the native library version loadable by the application because an 32 bit application cannot load 64 bit dll, likewise an 64bit application cannot load and run 32bit dll.

Of course this situation can be alleviated by defining 32bit and 64bit versions of the functions exported by the dll in separate classes and depending on the platform you can select the right version on run time. Obviously, defining a second class for the same set of functions leads to code duplication. To overcome this situation, this answer in stackoverflow provides some level of solution. It mentions about using SetDllDirectory Windows API functions to set up a dll search path for the executable. Changing default dll search behavior with such a call could be dangerous. According to MSDN, each call to the SetDllDirectory function replaces the previously set directory. Because of that, loading more than one dll where each is in different directory is still problematic.

Using Reflection.Emit based approach

The proposed solution aims to bring together mentioned solutions and provide a clean way to load/unload native libraries in 32bit and 64bit environments. The idea is to have the user define an interface with native method signatures decorated with a special attribute called ImportFunction. Then by using a provided factory class the user will be able to generate an implementation for the defined interface. Our code will tie the interface to the native implementation by use of Reflection.Emit code.

Attribute class is a simple one; it allow declaration of ModuleName, CharSet and CallingConvention parameters for the native library.

public class ImportFunction : System.Attribute    {
        
    public string ModuleName { get; set; }

    public CharSet CharSet { get; set; }

    public CallingConvention CallingConvention { get; set; }

    public ImportFunction(string moduleName)
    {
        if (String.IsNullOrEmpty(moduleName))
            throw new ArgumentException("Module name null");

        this.ModuleName = moduleName;
    }
} 

Interfaces will be extended from a base interface called INativeImport, which also extended from IDisposable. This interface is needed since it is required that the objects created can also be disposed.

//
// Define INativeImport
// 
public interface INativeImport : IDisposable // INativeImport extends IDisposable 
{ 
    // No method here
}  

We will also have an abstract base class that implements INativeImport which we call NativeImportBase. IDisposable interface will be implemented in this class so that it can be shared by all the derived classes. Dll loading will be performed by PInvoke methods therefore Dispose will perform dll unloading by keeping track of loaded module names with an internal member of type List<string>. Also, two native Win32 functions required to perform dll unloading are also imported in this class. GetModuleHandleEx gives us the native handle to the loaded dll file. FreeLibrary decreases the reference count and if reference count equals to zero, actually unloads it.

Our base class NativeImportBase implementation looks like this:

public class NativeImportBase : IDisposable
{
    //
    // List of module names
    private List<string> moduleNames;
 
    //
    // Define native functions that need to unload native libraries
    //
    [DllImport("kernel32.dll")]
    private static extern bool FreeLibrary(IntPtr hModule);
 
    static uint GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002;
 
    [DllImport("kernel32.dll")]
    static extern bool GetModuleHandleEx(uint flags, string moduleName, out IntPtr hModule);
 
    public NativeImportBase(List<string> pModuleNames)
    {
        if (pModuleNames == null)
            throw new ArgumentException("Module names null");
 
        moduleNames = pModuleNames;
    }
 
    // Implement Dispose pattern in base class
    //
    ~NativeImportBase()
    {
        Dispose(false);
    }
 
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
 
    private void Dispose(bool disposing)
    {
        //
        // Dispose loaded modules
        //

        IntPtr hModule = new IntPtr();
        foreach (string moduleName in moduleNames)
        {
            if (true == GetModuleHandleEx(
                GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
                    moduleName, out hModule) && hModule != IntPtr.Zero)
            {
                if (FreeLibrary(hModule) == false)
                {
                    throw new SystemException(
                        String.Format("Unable to unload module {0}. Error code: {1}",
                            moduleName,
                            Marshal.GetLastWin32Error()));
                }
             }
         }
    }
} 

In order to create an instance we need to provide a factory class since types will be dynamically generated. Our factory class will be called NativeImport. It has a single public method with the following signature.

public static T Create<T>(string modulePath = "") where T : INativeImport 

This is the function clients will use to create instances that implement their defined interfaces. String parameter modulePath is used indicate to location of native library in file system. This parameter can be used to load different versions of same dll such as 32bit or 64bit depending on the platform.

The Create method makes use of Reflection.Emit calls to define a dynamic assembly, then a define a dynamic module both of which are named after generated GUIDs to avoid conflicts. After that it starts to define a type T which implements the given interface. Generated type will have a single private constructor which will take List<string> as parameter. This parameter will contain the module paths that will be unloaded when the object is disposed. Default constructor is defined as follows:

private static void DefineConstructor(TypeBuilder tBuilder)
{
    // Ctor takes List<string> as module file names to unload in Dispose

    Type [] ctorParameters = new Type[] { typeof(List<string>) };

    ConstructorBuilder ctorBuilder = tBuilder.DefineConstructor(
        MethodAttributes.Public, CallingConventions.Standard, ctorParameters);

    ILGenerator ctorIL = ctorBuilder.GetILGenerator();
    ctorIL.Emit(OpCodes.Ldarg_0);
    ctorIL.Emit(OpCodes.Ldarg_1);
    ctorIL.Emit(OpCodes.Call, typeof(NativeImportBase).GetConstructor(ctorParameters));    
    ctorIL.Emit(OpCodes.Ret);
}       

For each method declared using ImportAttribute class, the new type contains a private static PInvoke method and a public proxy method that forward methods calls to this specific PInvoke method. The PInvoke method is declared using DefinePInvokeMethod. Definition of this function is below:

private static MethodBuilder DefinePInvokeMethod(TypeBuilder tBuilder, string methodName, string moduleName, CallingConvention callingConvention, CharSet charset, Type returnType, Type [] parameters)
{
    MethodBuilder pInvokeMethod = tBuilder.DefinePInvokeMethod(methodName, moduleName,
        MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.PinvokeImpl,
        CallingConventions.Standard, 
        returnType,
        parameters,
        callingConvention, charset);
        
    if(!returnType.Equals(typeof(void)))
        pInvokeMethod.SetImplementationFlags(
            pInvokeMethod.GetMethodImplementationFlags() | 
            MethodImplAttributes.PreserveSig);
 
    return pInvokeMethod;
}   

The proxy method is defined using DefineProxyMethod. In this method, 0th parameter to the method is skipped since we will be calling a static function which doesn't have an instance object available. Also note that this method is defined as generic so that we can override the methods declared in user supplied interface.

private static MethodBuilder DefineProxyMethod<T>(TypeBuilder tBuilder, string methodName, Type returnType, Type[] parameters, MethodBuilder proxiedMethod)
{
    MethodBuilder proxyMethod = tBuilder.DefineMethod(methodName,
        MethodAttributes.Public | MethodAttributes.Virtual,
        returnType,
        parameters);
 
    ILGenerator proxyMethodIL = proxyMethod.GetILGenerator();
 
    //
    // Pass parameters, since this is proxy for a static method, skip first arg
    //
    for(int i=1; i <= parameters.Length; ++i)
    {
        switch(i) {
            case 1:
                proxyMethodIL.Emit(OpCodes.Ldarg_1);
                break;
             case 2:
                 proxyMethodIL.Emit(OpCodes.Ldarg_2);
                 break;
             case 3:
                 proxyMethodIL.Emit(OpCodes.Ldarg_3);
                 break;
             default:
                 proxyMethodIL.Emit(OpCodes.Ldarg, i);
                 break;
        }
    }
 
    proxyMethodIL.Emit(OpCodes.Call, proxiedMethod);
    proxyMethodIL.Emit(OpCodes.Ret);
 
    //
    // Override method defined in the interface
    //
    tBuilder.DefineMethodOverride(proxyMethod, typeof(T).GetMethod(methodName));
 
    return proxyMethod;
} 

Using the code

Using the code is pretty straightforward. To demonstrate the code usage assume that there is a native library called NativeLib.dll with a function defined and exported as following:

__declspec(dllexport) unsigned int AppendString(wchar_t *lpwString1, unsigned int nSize1, wchar_t *lpwString2, unsigned int nSize2)
{
    return swprintf_s(lpwString1, nSize1, L"%s%s", lpwString1, lpwString2);
}   

In order to use that function in our code, we first declare an interface called IMyStringApi as follows:

public interface IMyStringApi : INativeImport
{
    [ImportFunction("NativeLib.dll", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Unicode)]
    uint AppendString(StringBuilder str1, uint nSize1, StringBuilder str2, uint nSize2);
} 

Now depending on the platform we are now, we can create an instance that implements this interface and start using the object. Assume that we have two versions of our dll, one for 32bit that is in x86 directory one for 64 bit that is in x64 directory next to our executable. To select the right version, we check the platform for bitness and we just pass the full path to the corrent dll when calling our Create function.

string path = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
            
IMyStringApi myStringApi;

//
// Create as instance depending on the platform 
//
if (IntPtr.Size == 4)
    myStringApi = NativeImport.Create<IMyStringApi>(Path.Combine(path, "x86"));
else
    myStringApi = NativeImport.Create<IMyStringApi>(Path.Combine(path, "x64"));
 
StringBuilder str1 = (new StringBuilder(255)).Append("Hello");
StringBuilder str2 = new StringBuilder("World");

// 
// Call native function 
//  
myStringApi.AppendString(str1, (uint)str1.Capacity, str2, (uint)str2.Capacity);
Console.WriteLine(str1);

// 
// Dispose after use
//
myStringApi.Dispose();  

There are two projects in the code downloads. NativeLib is a simple dll which contains an exported function for demonstration. NativeImport project is written in C# and contains the code explained here with an example usage.

History

Download files reorganized. v5

Reformatted code. v4

Reworded some paraghraphs. v3

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

mll5

United States United States
No Biography provided

Comments and Discussions

 
Questionhow to release resources? Pinmemberytyet200530-Jul-14 2:33 
AnswerRe: how to release resources? Pinmembermll52-Sep-14 22:09 
QuestionAnother vote of 5, however.. PinmemberKD0YU7-Apr-14 13:47 
GeneralMy vote of 5 PinprofessionalVolynsky Alex3-Apr-14 20:13 

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 | Terms of Use | Mobile
Web03 | 2.8.141030.1 | Last Updated 3 Apr 2014
Article Copyright 2014 by mll5
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid