Richard Smith's Blog

The thoughts of an easily distracted software developer...

Runtime architecture detection & DllImport

I have been playing around with the excellent Math.NET Numerics lately and wanted to see if I could use native MKL libraries with our product. A requirement of this would be that we could ship an Any CPU version of the application and detect which native library to load. Normally this is fairly easy with .NET assemblies as you can just hook into the AppDomain.AssemblyResolve event. You then do something like this:

private Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    Assembly asm = null;
    string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    string asmName = string.Format("{0}.dll", args.Name);
    string asmPath = string.Empty;

    if (Environment.Is64BitProcess)
        asmPath = Path.Combine(appPath, "x64", asmName);
    else
        asmPath = Path.Combine(appPath, "x86", asmName);

    if (File.Exists(asmPath))
        asm = Assembly.LoadFile(asmPath);

    return asm;
}

This unfortunately doesn’t work with unmanaged assemblies referenced with a DllImport attribute. These assemblies are loaded using the standard windows loader which lives outside .NET. What you can do however is use the SetDllDirectory system call to insert an additional search path for the windows loader before loading takes place. You can then call SetupNativeSearchPaths on start up (or some other time before the unmanaged assembly is loaded):

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetDllDirectory(string pathName);

public static bool SetupNativeSearchPaths()
{
    string appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    string asmPath = string.Empty;
    if (Environment.Is64BitProcess)
        asmPath = Path.Combine(appPath, "x64");
    else
        asmPath = Path.Combine(appPath, "x86");

    return SetDllDirectory(asmPath);
}