Dynamically Loading Unmanaged DLLs And Linking Using Reflection

 

My word, what a nerd-geek title ! But what does it mean ? Well, I wanted to load an unmanaged DLL in my C# project and link to its functions at runtime. Thing is, there are a 100+ functions to be linked to and life’s too short to type in the code to link each one in turn – to say nothing of error prone! So, lets see if reflection can come to the rescue.

OK, so lets understand the issue here. In my case, I was loading the main API dll for VLCPlayer and all the functions I want begin with “libvlc_”. It’s discussed more fully at Hosting VLC Player In .NET Winforms but the gist of it is for every function I want to call I need a delegate and an instance of that delegate:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
private delegate string libvlc_get_version();
static libvlc_get_version _libvlc_get_version;

Then when my dll is loaded (using Kernal32′s LoadLibrary) I’ll use GetProcAddress (also kerna32) to get a pointer:

IntPtr hDll = LoadLibrary(pathOfDllToBeLoaded);

    .... some stuff and then ...

IntPtr procAddress = GetProcAddress(hDll, libvlc_get_version);

For now, let’s assume this succeeds. Now to create the delegate that we can use to call the VLC dll’s function:

 _libvlc_get_version = (libvlc_get_version)Marshal.GetDelegateForFunctionPointer(procAddress, typeof( libvlc_get_version);

These last two lines – getting procAddress and setting _ libvlc_get_version will essentially be repeated for every linked function. Sure, cut and paste helps but before long you will do something dumb like:

IntPtr procAddress = GetProcAddress(hDll, libvlc_get_version);
_libvlc_video_get_size = (libvlc_video_get_size)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(libvlc_video_get_size);

Yep, courtesy of cut-n-paste wrong dll function for delegate … and much time spent head scratching instead of getting on with the important things in life such as making a cuppa. Now, thing is, all the functions I wanted to link to began “libvlc_” and I created an instance with the same name but prefixed with an underscore. Such consistency cries out Reflection !

Now, I’m going to assume you are an OOP programmer so will be keeping all these nasty delegates private and as you are loading a dll you’ll only be doing it once so the delegate instances should be static right ? OK, then we start with:

var fields = dll.GetType().GetFields(BindingFlags.Static | BindingFlags.NonPublic);

Now we just such the fields for fields whose names begin with “_libvlc”:

foreach (var field in fields)
{
    if (field.Name.IndexOf(@"_libvlc_", StringComparison.InvariantCulture) >= 0)
    {
        ... wire it up ! ...
    }
}

Now to write the “wire it up” bit. Since the delegate name is the same as the name of the function we are calling we can get the function pointer easily:

var procAddress = GetProcAddress(hDll, field.FieldType.Name;

Assuming that succeeds we can then create the delegate and assign it to the field.

field.SetValue(null, Marshal.GetDelegateForFunctionPointer(procAddress, field.FieldType));

And that’s it ! Now, every time I define a new delegate and associated field it’ll get automatically wired up. The complete code is:

var fields = dll.GetType().GetFields(BindingFlags.Static | BindingFlags.NonPublic);

foreach (var field in fields)
{
   if (field.Name.IndexOf(@"_libvlc_", StringComparison.InvariantCulture) >= 0)
   {
      var vlcFunctionName = field.FieldType.Name;
      var procAddress = GetProcAddress(dll._hDll, vlcFunctionName);
      field.SetValue(null, Marshal.GetDelegateForFunctionPointer(procAddress, field.FieldType));
   }
}

One final job : where the fields are actually defined, the compiler will generate warning CS0649 (“<field> is never assigned to, and will always have its default value null)”. It’s a fair cop, the compiler can’t reasonably be expected to know they are initialised via reflection. Therefore, we just need to put all the declarations in one place and wrap them with:

#pragma warning disable 0649

// ... Declare all fields here ...

#pragma warning restore 0649

Job done, time for a tea break … or is it?

Handling Missing Functions

Now, in my case, I’m talking to a 3rd party dll and lets assume I’m not in control of the version that’s being loaded; functions come and functions go, so perhaps a bit of error checking wouldn’t go amiss.

Currently a missing dll function will result in a null argument exception in GetDelegateForFunctionPointer(). I don’t think that is appropriate for the client to get that type of exception, so add a check for function not found and throw a slightly (but not perfectly) more appropriate DllNotFoundException:

If( procAddress == IntPtr.Zero )
    throw new DllNotFoundException("Dll does not contain required VLC function '" + vlcFunctionName + "'");

But we can do even better. Some DLL functions will always be required – for example, in VLC everything hinges on creating a “session” and without this the DLL would be useless. However, some other functions – for example reading the size of a video frame – I only added out of curiosity and only use in debugging so it’d be a shame to blow up just because a function I didn’t actually need were missing. Wouldn’t it be good to somehow mark functions that were optional ?

Applying Custom Attributes To Delegate Instances

One of the many things I like about C# (.NET) is being able to mark things with custom attributes. Lets create one that we can apply to functions we consider optional:

[AttributeUsage(AttributeTargets.Field)]
internal class VlcOptionalAttribute : Attribute
{
}

Wasn’t that easy ! And now we apply that attribute to optional functions:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int libvlc_video_get_size(IntPtr hPlayer, uint num, out uint width, out uint height);
[VlcOptional]
static libvlc_video_get_size _libvlc_video_get_size;

… and modify the initialise-via-reflection such that when a dll entry point is missing it first checks for this attribute and does not throw an exception if the field has this attribute. (Note: when applying the attribute you can miss off the trailing word “Attribute”).

if (procAddress == IntPtr.Zero)
{
    var procIsOptional = false;
    foreach (var attrib in field.GetCustomAttributes(false))
    {
        if (attrib is VlcOptionalAttribute)
        {
                procIsOptional = true;
        }
    }

    if (!procIsOptional)
    {
        FreeLibrary(hDll);	// Also in kernel32
        throw new DllNotFoundException("Dll does not contain required VLC function '" + vlcFunctionName + "'");
    }
}
else
{
    field.SetValue(null, Marshal.GetDelegateForFunctionPointer(procAddress, field.FieldType));
}

By the way, note the “gotcha” that initially had me scratching my head. “field” has an “Attributes” field and I was fully expecting my custom attribute to appear in there. But it didn’t. No, what was actually needed was I call field.GetCustomAttributes().

Anyhow, thats a wrap, finally, time for that well earned cuppa … what ? You don’t like reflection ‘cos it’s slow !?

I Don’t Like Reflection, It’s Slow

OK then, lets add a stopwatch around all that and dump the results out to a Debug.WriteLine():-see what we get:

DEBUG build: “Of 25 fields, 22 were vlc fields that were linked, and took 675 milliseconds”.
RELEASE build: “Of 25 fields, 22 were vlc fields that were linked, and took 3 milliseconds”

I can definitely live with that in release builds, though I’m guessing that by the time I have 100 vlc fields I might be wanting a faster PC for debug builds.

Right ! Push off ! I’m gasping for a cuppa.
cuppa

Leave a Reply