3

I have A C# Visual Studio 2012 Solution that relies on a native dll that I use PInvoke to access. When I deploy the app I will have to ensure that this Dll is in the app folder.

Is there anyway I can merge this Dll into the executable?

perhaps as a resource?

I have heard of ILMerge but I am told it cant cope with native code.

Any help would be appreciated.

Steven Wood
  • 2,675
  • 3
  • 26
  • 51

2 Answers2

6

You can create a Setup package project with Visual Studio that deploys all your files to the correct location or use other third party packaging software (like full InstallShield or alternatives)

However, your question reminds me on the Open Hardware Monitor project where they include drivers as embedded resource and extract them when the user starts the application. It works like this: they've added WinRing0.sys and WinRing0x64.sys to the project and set their Build Action to Embedded Resource, then they have a method that extracts the driver from the resource:

private static bool ExtractDriver(string fileName) {
  string resourceName = "OpenHardwareMonitor.Hardware." +
    (OperatingSystem.Is64BitOperatingSystem() ? "WinRing0x64.sys" : 
    "WinRing0.sys");

  string[] names =
    Assembly.GetExecutingAssembly().GetManifestResourceNames();
  byte[] buffer = null;
  for (int i = 0; i < names.Length; i++) {
    if (names[i].Replace('\\', '.') == resourceName) {
      using (Stream stream = Assembly.GetExecutingAssembly().
        GetManifestResourceStream(names[i])) 
      {
          buffer = new byte[stream.Length];
          stream.Read(buffer, 0, buffer.Length);
      }
    }
  }

  if (buffer == null)
    return false;

  try {
    using (FileStream target = new FileStream(fileName, FileMode.Create)) {
      target.Write(buffer, 0, buffer.Length);
      target.Flush();
    }
  } catch (IOException) { 
    // for example there is not enough space on the disk
    return false; 
  }

  // make sure the file is actually writen to the file system
  for (int i = 0; i < 20; i++) {
    try {
      if (File.Exists(fileName) &&
        new FileInfo(fileName).Length == buffer.Length) 
      {
        return true;
      }
      Thread.Sleep(100);
    } catch (IOException) {
      Thread.Sleep(10);
    }
  }

  // file still has not the right size, something is wrong
  return false;
}

They're reading the resource into a buffer, write that buffer to disk and wait until the file has been flushed to disk.

Community
  • 1
  • 1
huysentruitw
  • 27,376
  • 9
  • 90
  • 133
1

My solution is conceptually similar to the one presented by Wouter.

It's what we use in our own app, and we can use native/mixed-mode and c# dlls all embedded in the same .exe.

It extracts the dlls into a temp dir everytime the application is run. Obviously you might not want to do this in the production version, where the dlls will be stable; you might choose a different directory there (probably somewhere in %AppData%). It will use an existing dll with the same version number, though (e.g. it's only done the first time when opening the app multiple times between booting the computer).

Since we're doing

AppDomain.CurrentDomain.AssemblyResolve += (sender, args)

this function is getting called wherever the system tries to resolve a dll. And since it's initalised in the static Program class, it all works automagically.

Program.cs:

namespace MyApp
{
    internal class Program
    {
        static Program()
        {
            LoadAssemblyResource.Initialize("MyApp");
        }

        //....
    }
}  

LoadAssemblyResource.cs

namespace MyAppStartup
{ 
    public static class LoadAssemblyResource
    {
        private readonly static String _version_string =
            Assembly.GetExecutingAssembly().GetName().Version.ToString();

        private readonly static String _dll_path = Path.GetTempPath()
            + "\\MyApp\\" + _version_string;

        static public String last_error_msg = null;

        public static bool WriteBytesToFile(string filename, byte[] bytes)
        {
            try
            {
                var fs = new FileStream(filename, FileMode.Create, FileAccess.Write);
                fs.Write(bytes, 0, bytes.Length);
                fs.Close();
                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine("Writing file failed. Exception: {0}", e.ToString());
            }
            return false;
        }

        public static Assembly LoadUnsafe(String assembly_name, Byte[] assembly)
        {
            if (!Directory.Exists(_dll_path))
            {
                Directory.CreateDirectory(_dll_path);
                Console.WriteLine("Created tmp path '" + _dll_path + "'.");
            }

            String fullpath = _dll_path + "\\" + assembly_name;
            if (!File.Exists(fullpath))
            {
                Console.WriteLine("Assembly location: " + fullpath + ".");
                if (!WriteBytesToFile(fullpath, assembly))
                     return null;
            }

            return Assembly.UnsafeLoadFrom(fullpath);
        }

        public static void Initialize(String exe_name)
        {
            AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
            {
                String assembly_name = new AssemblyName(args.Name).Name + ".dll";
                String resource_name = exe_name + "." + assembly_name;

                using (var stream = 
                    Assembly.GetExecutingAssembly().GetManifestResourceStream(resource_name))
                {
                    if (stream == null)
                        return null;

                    Byte[] assembly_data = new Byte[stream.Length];
                    stream.Read(assembly_data, 0, assembly_data.Length);
                    try
                    {
                        Assembly il_assembly = Assembly.Load(assembly_data);
                        return il_assembly;
                    }
                    catch (System.IO.FileLoadException ex)
                    {
                        // might have failed because it's an mixed-mode dll.
                        last_error_msg = ex.Message;
                    }

                    Assembly mixed_mode_assembly = LoadUnsafe(assembly_name, assembly_data);
                    return mixed_mode_assembly;
                }
            };
        }
    }

}

Community
  • 1
  • 1
Wilbert
  • 7,251
  • 6
  • 51
  • 91