使用本地Dll部署C#解决方案为单个EXE文件

3
我有一个依赖于本地 DLL 的 C# Visual Studio 2012 解决方案,我使用 PInvoke 访问它。当我部署应用程序时,我必须确保此 DLL 在应用程序文件夹中。
是否有办法将此 DLL 合并到可执行文件中?也许作为资源?
我听说过 ILMerge,但我被告知它无法处理本地代码。
感谢任何帮助。

2
是的,这是可能的。每个人都将可执行文件命名为“setup.exe”。你肯定以前用过其中之一。有很多安装程序创建工具可供选择,VS提供InstallShield LE。你需要去购物了。 - Hans Passant
这个 DLL 是用 DotNet Framework 创建的吗? - Mohamed Salemyan
@MohamedSalemyan,他明确表示这是原生的 :) - Moo-Juice
如果您不想花费太多,可以使用WiX。 - Christian Sauer
嗨,我刚解决了同样的问题...请看这里提供的解决方案:https://dev59.com/kmw05IYBdhLWcg3w9mdW - mutanic
显示剩余6条评论
2个回答

6
你可以使用Visual Studio创建一个安装包项目,将所有文件部署到正确的位置,或者使用其他第三方打包软件(如完整的InstallShield或替代方案)。
然而,你的问题让我想起了开源硬件监控器项目,他们将驱动程序作为嵌入式资源包含在内,并在用户启动应用程序时提取它们。它的工作方式是这样的:他们将WinRing0.sysWinRing0x64.sys添加到项目中,并将它们的构建操作设置为嵌入式资源,然后他们有一个方法从资源中提取驱动程序:
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;
}

他们正在将资源读入缓冲区,将该缓冲区写入磁盘并等待文件被刷新到磁盘。

1

我的解决方案在概念上类似于Wouter提出的解决方案。

这是我们自己应用程序中使用的方法,我们可以在同一个.exe文件中嵌入本地/混合模式和c# dlls。它会在每次运行应用程序时将dlls提取到临时目录中。显然,在生产版本中您可能不想这样做,因为dlls将是稳定的;您可能会选择不同的目录(可能在%AppData%中的某个位置)。但是它将使用具有相同版本号的现有dll(例如,仅在多次打开应用程序之间启动计算机时第一次执行)。

由于我们正在进行

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

这个函数在系统尝试解析dll时被调用。由于它在静态Program类中初始化,所以一切都可以自动运行。

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;
                }
            };
        }
    }

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接