2

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();
    }
}
Ezhirko
  • 23
  • 1
  • 6

3 Answers3

1

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...

}
Avi Turner
  • 10,234
  • 7
  • 48
  • 75
  • Yes Utility.dll is supposed to be a common dll which will be used by several win forms. So it will be bad to copy that dll to its bin folder by all the exe's that are using it. I am currently using reflection to load it dynamically. i am using "AssemblyResolve" in program.cs to load it dynamically. but since i am inheriting that interface i am facing issue. if i am not inheriting then, i am successfully able to load that dll dynamically. Is there any other way to solve this? – Ezhirko Oct 22 '13 at 06:20
  • You can try and load it dynamically in the Static constructor, this way it will be called before any object instantiates. You can also register it to the GAC, or add it's location to Environment Path. Still I would prefer to copy it locally, this will allow you to avoid issues that might occur if you update the dll + some of the forms, but not all of them. – Avi Turner Oct 22 '13 at 06:57
  • Where do i add a static constructor here to load assembly ? – Ezhirko Oct 22 '13 at 08:52
  • Wow, Thank you so much Avi Turner. It worked. Your awesome :) – Ezhirko Oct 22 '13 at 10:52
0

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?.

Community
  • 1
  • 1
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • I have checked out the link which you have suggested. Since the folder in which i place my Utility.dll is a common folder and not sub directory, i cannot use probing path. i tried using AppDomainSetup.PrivateBinPath before instantiating Form1. It did not work. I get the same file not found exception when i try to run my solution. The break point at first line of static void Main() is not hit at all. i get exception even before that. – Ezhirko Oct 22 '13 at 06:36
  • Could you please let me know how to Load assembly first and then instantiate classes that depend on it ? – Ezhirko Oct 22 '13 at 06:37
  • @Ezhirko - Read on `Assembly.Load` - you'll need that anyway since you want to use non-default approach to sharing assemblies. I'm not sure what more information you need - `Load` necessary assemblies first, `new` classes that use types from these assemblies after loading. – Alexei Levenkov Oct 22 '13 at 06:40
  • I understand your suggestion. But problem is execution of code is failing in the beginning it self. I am not getting break point hit at the very first line in static void Main(). The code execution throws exception when i run the project it self. Please help me on this. – Ezhirko Oct 22 '13 at 06:51
  • @Ezhirko "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)." – Alexei Levenkov Oct 22 '13 at 06:53
  • @Ezhirko You can try and load the dll's in the static constructor. – Avi Turner Oct 22 '13 at 07:07
0

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.

Baaleos
  • 1,703
  • 12
  • 22