How to reference C# dll located in different folder to a C# winforms exe in different folder which uses Interface exposed by that dll

StackOverflow https://stackoverflow.com/questions/19510228

Question

I have a C# windows forms that uses a Utility.dll located in different folder than that of the EXE location. Utility.dll contains a class UtilityClass and an interface ILoadString. When i do not inherit ILoadString interface in my Form1.cs class, i am successfully able to load the Utility.dll through AppDomain.AssemblyResolve Event in Program.cs.

The problem arises when i try to inherit ILoadString interface in Form1.cs. When i try to run the project, i get an FileNotFoundException from visual studio saying "Could not load file or assembly 'Utility, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified." The control does not even come to static void Main() in Program.cs. I think CLR is trying to load Utility.dll in the beginning itself as my Form1.cs is inheriting ILoadString.

Note: In Add reference Utility.dll 's copy local is set to "false". so Exe folder do not contain this dll.

How do i load Utility.dll from other folder in this case? Any help appreciated.

Thanks in advance.

I am pasting my code below.

using System.Reflection;

namespace SampleForm
{
    static class Program
    {
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        AppDomain currentDomain = AppDomain.CurrentDomain;
        currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
    {
        Assembly MyAssembly, objExecutingAssemblies;
        string strTempAssmbPath = "";
        objExecutingAssemblies = Assembly.GetExecutingAssembly();
        AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();
        foreach (AssemblyName strAssmbName in arrReferencedAssmbNames)
        {
            if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
            {
                strTempAssmbPath = "D:\\Ezhirko\\SampleForm\\bin\\Common\\" +
                    args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
            }
        }       
        MyAssembly = Assembly.LoadFrom(strTempAssmbPath);
        return MyAssembly;
    }
}

My Windows form here....

using Utility;

namespace SampleForm
{
    public partial class Form1 : Form, ILoadString
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
        string Name = string.Empty;
        UtilityClass obj = new UtilityClass();
        Name = obj.GetName();
        this.lblHello.Text = Name;
        ChangeName();
    }


    public void ChangeName()
    {
        this.lblHello.Text = "InterFace Called !";
    }
}

This is UtilityClass.cs from Utility.dll

namespace Utility
{
    public class UtilityClass
    {
        private string sName = string.Empty;

        public UtilityClass()
        {
            sName = "My Name";
        }

        public string GetName()
        {
            return sName;
        }
    }
}

This is interface ILoadString.cs from Utility.dll

namespace Utility
{
    public interface ILoadString
    {
        void ChangeName();
    }
}
Was it helpful?

Solution

Is there a specific reason you have copy local is set to "false"?
It think you would be better off setting it to true.
Another option would be copying it to your bin folder using build events, or loading it dynamically using reflection.
But again, as I said, I think it would be better to simply set copy local to true.

EDIT: (as per Ezhirko's Comment)

You can add loading the assemblies on the static constructor of Program or in SampleForm:

static class Program
{
    static Program()
    {
        //Loads assemblies. 
    }

   //the rest of your code...

}

OTHER TIPS

Load assembly first and than instantiate classes that depend on it. Your current code will have to delay creation of Form1 till you loaded assemblies (and perform it in separate method as JITing the method requires class' methadata).

Other option: you can configure search path to include "other location" (if it is subfolder of your app) as probing path as covered in How to add folder to assembly search path at runtime in .NET?.

I don't know if it is intentional, but your foreach loop generates a path for each assembly, but you only ever load the last assembly, as the Assembly.Load is outside of the loop.

foreach (AssemblyName strAssmbName in arrReferencedAssmbNames)
        {
            if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
            {
                strTempAssmbPath = "D:\\Ezhirko\\SampleForm\\bin\\Common\\" +
                    args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
            }
        }       
        MyAssembly = Assembly.LoadFrom(strTempAssmbPath);
        return MyAssembly;

My guess is that you are trying to load a specific assembly in this event. I use this code in my own applications: It might be easier to use.

This code was wrote with the intent of allowing me to resolve assemblies from Embedded resources within the DLL/APPLICATION itself. If done right, you can even compress the dll's and have them decompressed at run time.

Note: this one might be easier to use, since it avoids that looping strategy you were using.

        /// <summary>
        /// This is the handler for failed assembly resolution attempts - when a failed resolve event fires, it will redirect the assembly lookup to internal 
        /// embedded resources. Not necessary for this method to ever be called by the user.
        /// </summary>
        /// <param name="sender">not important</param>
        /// <param name="args">automatically provided</param>
        /// <returns>returns the assembly once it is found</returns>
        private static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
        {
            string[] toSplitWith = { "," };
            //New improvement - Allows for ANY DLL to be resolved to a local memory stream.
            bool bCompressed = true;
            string sFileName = args.Name.Split(toSplitWith, StringSplitOptions.RemoveEmptyEntries)[0] + ".dll";
            string sPath = "NameSpace.Resources.";

            Assembly thisAssembly = Assembly.GetExecutingAssembly(); //Gets the executing Assembly
            using (Stream input = thisAssembly.GetManifestResourceStream(sPath + sFileName)) // Acquire the dll from local memory/resources.
            {

                return input != null ? Assembly.Load(StreamToBytes(input)) : null; // More bitwise operators - if input not Null, return the Assembly, else return null.
            }
        }

While I use sPath = "NameSpace.Resources";
you could however point it at another folder location on your computer, and then just Assembly.LoadFrom, instead of worrying about the GetManifestResourceStream();

Also - about the resolve event being fired immediately. If you want to ensure that the event gets fired 'after' the resolve handler has been setup, you need to embed the property/field within a subclass that gets instantiated after the resolve handler is setup. If you have it as a property on the main form, those properties will try to be created at parent class creation time. Even if they are null, the type is used, and if the type is used, it will try to pull the dll into memory at the same time. So if you put the declaration into a sub class, only instantiating it after the resolve is setup, then it should not bother you about the DLL being missing.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top