Saturday, September 3, 2016

C#: Target Any Version of Oracle.DataAccess.dll in Runtime

Writing code to resolve Oracle.DataAccess.dll on runtime.
When you deploy application that uses Oracle.DataAccess.dll reference, target computer and developing computer must have same version of dll. If target computer install different version of dll, your application will say it could not find the extact match version of dll and raise the error.

This is major issue for me. I use very simple method like querying table that I know it is work on across multiple version of Oracle.DataAccess.dll, and I found many users already install it but in different version. It would be nice if I can use dll that is already installed on client computer.

After some researching, I found that C# implemented mechanism to resolve dll on runtime. When your app cannot find specific version of Oracle.DataAccess.dll, your app will call AppDomain.CurrentDomain.AssemblyResolve delegate.

So, All you have to do is to create AssemblyResolve delegate method that return the assembly. Here is the code.
public static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    Assembly MyAssembly;
    string strTempAssmbPath = "";
 
    //find the path of Oracle.DataAccess.dll
    if (args.Name.Contains("Oracle.DataAccess"))
       strTempAssmbPath = GetAssemblyPath("Oracle.DataAccess");

    //Load the assembly from the specified path.
    MyAssembly = Assembly.LoadFrom(strTempAssmbPath);

    //Return the loaded assembly.
    return MyAssembly;
}

public static string GetAssemblyPath(string name)
{
    if (name == null)
        throw new ArgumentNullException("name");

    string finalName = name;
    AssemblyInfo aInfo = new AssemblyInfo();
    aInfo.cchBuf = 1024; // should be fine...
    aInfo.currentAssemblyPath = new String('\0', aInfo.cchBuf);

    IAssemblyCache ac;
    int hr = CreateAssemblyCache(out ac, 0);
    if (hr >= 0)
    {
        hr = ac.QueryAssemblyInfo(0, finalName, ref aInfo);
        if (hr < 0)
            return null;
    }

    return aInfo.currentAssemblyPath;
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("e707dcde-d1cd-11d2-bab9-00c04f8eceae")]
private interface IAssemblyCache
{
    void Reserved0();

    [PreserveSig]
    int QueryAssemblyInfo(int flags, [MarshalAs(UnmanagedType.LPWStr)] string assemblyName, ref AssemblyInfo assemblyInfo);
}
[StructLayout(LayoutKind.Sequential)]
private struct AssemblyInfo
{
    public int cbAssemblyInfo;
    public int assemblyFlags;
    public long assemblySizeInKB;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string currentAssemblyPath;
    public int cchBuf; // size of path buf.
}
[DllImport("fusion.dll")]
private static extern int CreateAssemblyCache(out IAssemblyCache ppAsmCache, int reserved);
Finding path of Oracle.DataAccess.dll is easy. You just call GetAssemblyPath method. Thanks to this website

In Main() method, move your code to mainTask() method and replace Main() method with this code
public static void Main(string[] args)
{
    //Main method MUST NOT HAVE ANY CODE other than this.
    AppDomain currentDomain = AppDomain.CurrentDomain;
    currentDomain.AssemblyResolve += new ResolveEventHandler(currentDomain_AssemblyResolve);

    mainTask();
}

public static void mainTask()
{
    //move your old code from Main to this method.
}


Happy Coding!

No comments:

Post a Comment