1

I'm trying to load dll libraries during runtime using the following code so that I don't have to provide the user with lot of dll files along with the main executable file. I have inlude all the dll files as an embedded resource and also in the reference part I have include them and have set the CopyLocal property to false. But the problems here are:
1. All the dll are getting copied to Bin\Debug folder
2. I'm getting FileNotFoundException.
I did lot of searches to get these things resolved and finally I'm here. I got a similar code here but still couldn't do anything. What should I do to prevent this exception...?? Is there a better way to do the same thing for a Windows Form Application(Not WPF)...??

using System;
using System.Linq;
using System.Windows.Forms;
using System.Diagnostics;
using System.Reflection;
using System.Collections.Generic;
using System.IO;

namespace MyNameSpace
{
    static class Program
    {
        static int cnt;
        static IDictionary<string, Assembly> assemblyDictionary;
        [STAThread]
        static void Main()
        {
            AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
            if (cnt != 1)
            {
                cnt = 1;
                Assembly executingAssembly = Assembly.GetExecutingAssembly();
                string[] resources = executingAssembly.GetManifestResourceNames();
                foreach (string resource in resources)
                {
                    if (resource.EndsWith(".dll"))
                    {
                        using (Stream stream = executingAssembly.GetManifestResourceStream(resource))
                        {
                            if (stream == null)
                                continue;

                            byte[] assemblyRawBytes = new byte[stream.Length];
                            stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
                            try
                            {
                                assemblyDictionary.Add(resource, Assembly.Load(assemblyRawBytes));
                            }
                            catch (Exception ex)
                            {
                                MessageBox.Show("Failed to load: " + resource + " Exception: " + ex.Message);
                            }
                        }
                    }
                }
                Program.Main();
            }
            if (cnt == 1)
            {
                cnt = 2;
                System.Threading.Thread.CurrentThread.Priority = System.Threading.ThreadPriority.Highest;
                Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
                Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new MainForm());
            }
        }

        private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
        {            
            AssemblyName assemblyName = new AssemblyName(args.Name);

            string path = assemblyName.Name + ".dll";

            if (assemblyDictionary.ContainsKey(path))
            {
                return assemblyDictionary[path];
            }
            return null;
        }
    }
}

If I'm using something unnecessarily in my code then you can show me the right way... I'm a student working on Windows Form Application v4.0 project for my papers to be submitted.

Community
  • 1
  • 1
Benison Sam
  • 2,755
  • 7
  • 30
  • 40
  • Check the remarks section on the System.BadImageFormatException page on MSDN. There you will find the conditions that cause this exception to be thrown. If I were you I wouldn't waste time on this. Instead I would focus on the actual program. Also why do you change the thread priority? If there is no specific reason behind this choice then you shouldn't do it. It is best left at Normal (the default). – Panos Rontogiannis Nov 18 '12 at 22:54
  • Hmm... The priority thing, I was just checking the change in application user experience if priority is set to Highest, as u say I'll remove it, I mean keep it to deault. And my application is totally ready, I'm using this code just to embed my reference library dlls into the target EXE. And this thing, I urgently need. Please dont suggest me to use ILMerge, it always creates problems for me... – Benison Sam Nov 19 '12 at 05:08

2 Answers2

1

If it is still the case that you must do this, then use this OnResolveAssembly method. There is no need to preload them into an array if you don't want to. This will load them the first time they are actually needed.

Then just:

  • add the some.assembly.dll file to the project.
    • probably not a reference to the project's output
    • but the file that is the result of the DLL project.
  • mark it as a Resource in the file properties.

    // This function is not called if the Assembly is already previously loaded into memory.
    // This function is not called if the Assembly is already in the same folder as the app.
    //
    private static Assembly OnResolveAssembly(object sender, ResolveEventArgs e)
    {
        var thisAssembly = Assembly.GetExecutingAssembly();
    
        // Get the Name of the AssemblyFile
        var assemblyName = new AssemblyName(e.Name);
        var dllName = assemblyName.Name + ".dll";
    
        // Load from Embedded Resources
        var resources = thisAssembly.GetManifestResourceNames().Where(s => s.EndsWith(dllName));
        if (resources.Any())
        {
            // 99% of cases will only have one matching item, but if you don't,
            // you will have to change the logic to handle those cases.
            var resourceName = resources.First();
            using (var stream = thisAssembly.GetManifestResourceStream(resourceName))
            {
                if (stream == null) return null;
                var block = new byte[stream.Length];
    
                // Safely try to load the assembly.
                try
                {
                    stream.Read(block, 0, block.Length);
                    return Assembly.Load(block);
                }
                catch (IOException)
                {
                    return null;
                }
                catch (BadImageFormatException)
                {
                    return null;
                }
            }
        }
    
        // in the case the resource doesn't exist, return null.
        return null;
    }
    

-Jesse

PS: This comes from http://www.paulrohde.com/merging-a-wpf-application-into-a-single-exe/

Jesse Chisholm
  • 3,857
  • 1
  • 35
  • 29
  • Out of curiosity, where you define the "assemblyName" variable, could you hard-code a filename instead of pulling it dynamically? Like if my embedded DLL is named "Sharpie.dll", could I just define the assemblyName variable as "Sharpie"? – Omegacron Dec 30 '14 at 22:32
  • @Omegacron re: `hard coded filename` - This method is called for _any_ assembly that is not loaded already and for which the system cannot find it with a trivial search. So, no. The `e.Name` is the only way you can _know_ whether they are asking for the path to the `Sharpie.dll` or to some other assembly. – Jesse Chisholm Jan 02 '15 at 16:50
0

Try the following:

  • For each .dll resource:
    • If the file allready exists on the AppDomain.Current.BaseDirectory then continue to the next resource
    • Else save the resource to the AppDomain.Current.BaseDirectory. Do this in a try-catch and if it fails, notify the user. For this step to complete successfully you will need write access on the installation folder (usually a subfolder of "Program Files"). This will be solved by running the program as an administrator the first time only ao that the files are written on the file system.
  • Ιf the assemblies are referenced by your VS project then you do not have to load them yourself. To understand why this work's you will need to understand how assemblies are located by the CLR.
  • Else you will need to load each assembly yourself using one of the Assembly.Load that take either a string or and AssemblyName as a parameter.
Panos Rontogiannis
  • 4,154
  • 1
  • 24
  • 29