Embedding DLLs in a compiled executable

前端 未结 16 2707
情深已故
情深已故 2020-11-21 07:07

Is it possible to embed a pre-existing DLL into a compiled C# executable (so that you only have one file to distribute)? If it is possible, how would one go about doing it?<

相关标签:
16条回答
  • 2020-11-21 07:28

    To expand on @Bobby's asnwer above. You can edit your .csproj to use IL-Repack to automatically package all files into a single assembly when you build.

    1. Install the nuget ILRepack.MSBuild.Task package with Install-Package ILRepack.MSBuild.Task
    2. Edit the AfterBuild section of your .csproj

    Here is a simple sample that merges ExampleAssemblyToMerge.dll into your project output.

    <!-- ILRepack -->
    <Target Name="AfterBuild" Condition="'$(Configuration)' == 'Release'">
    
       <ItemGroup>
        <InputAssemblies Include="$(OutputPath)\$(AssemblyName).exe" />
        <InputAssemblies Include="$(OutputPath)\ExampleAssemblyToMerge.dll" />
       </ItemGroup>
    
       <ILRepack 
        Parallel="true"
        Internalize="true"
        InputAssemblies="@(InputAssemblies)"
        TargetKind="Exe"
        OutputFile="$(OutputPath)\$(AssemblyName).exe"
       />
    </Target>
    
    0 讨论(0)
  • 2020-11-21 07:30

    Just right-click your project in Visual Studio, choose Project Properties -> Resources -> Add Resource -> Add Existing File… And include the code below to your App.xaml.cs or equivalent.

    public App()
    {
        AppDomain.CurrentDomain.AssemblyResolve +=new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    }
    
    System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        string dllName = args.Name.Contains(',') ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll","");
    
        dllName = dllName.Replace(".", "_");
    
        if (dllName.EndsWith("_resources")) return null;
    
        System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());
    
        byte[] bytes = (byte[])rm.GetObject(dllName);
    
        return System.Reflection.Assembly.Load(bytes);
    }
    

    Here's my original blog post: http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/

    0 讨论(0)
  • 2020-11-21 07:31

    ILMerge can combine assemblies to one single assembly provided the assembly has only managed code. You can use the commandline app, or add reference to the exe and programmatically merge. For a GUI version there is Eazfuscator, and also .Netz both of which are free. Paid apps include BoxedApp and SmartAssembly.

    If you have to merge assemblies with unmanaged code, I would suggest SmartAssembly. I never had hiccups with SmartAssembly but with all others. Here, it can embed the required dependencies as resources to your main exe.

    You can do all this manually not needing to worry if assembly is managed or in mixed mode by embedding dll to your resources and then relying on AppDomain's Assembly ResolveHandler. This is a one stop solution by adopting the worst case, ie assemblies with unmanaged code.

    static void Main()
    {
        AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
        {
            string assemblyName = new AssemblyName(args.Name).Name;
            if (assemblyName.EndsWith(".resources"))
                return null;
    
            string dllName = assemblyName + ".dll";
            string dllFullPath = Path.Combine(GetMyApplicationSpecificPath(), dllName);
    
            using (Stream s = Assembly.GetEntryAssembly().GetManifestResourceStream(typeof(Program).Namespace + ".Resources." + dllName))
            {
                byte[] data = new byte[stream.Length];
                s.Read(data, 0, data.Length);
    
                //or just byte[] data = new BinaryReader(s).ReadBytes((int)s.Length);
    
                File.WriteAllBytes(dllFullPath, data);
            }
    
            return Assembly.LoadFrom(dllFullPath);
        };
    }
    

    The key here is to write the bytes to a file and load from its location. To avoid chicken and egg problem, you have to ensure you declare the handler before accessing assembly and that you do not access the assembly members (or instantiate anything that has to deal with the assembly) inside the loading (assembly resolving) part. Also take care to ensure GetMyApplicationSpecificPath() is not any temp directory since temp files could be attempted to get erased by other programs or by yourself (not that it will get deleted while your program is accessing the dll, but at least its a nuisance. AppData is good location). Also note that you have to write the bytes each time, you cant load from location just 'cos the dll already resides there.

    For managed dlls, you need not write bytes, but directly load from the location of the dll, or just read the bytes and load the assembly from memory. Like this or so:

        using (Stream s = Assembly.GetEntryAssembly().GetManifestResourceStream(typeof(Program).Namespace + ".Resources." + dllName))
        {
            byte[] data = new byte[stream.Length];
            s.Read(data, 0, data.Length);
            return Assembly.Load(data);
        }
    
        //or just
    
        return Assembly.LoadFrom(dllFullPath); //if location is known.
    

    If the assembly is fully unmanaged, you can see this link or this as to how to load such dlls.

    0 讨论(0)
  • 2020-11-21 07:31

    The excerpt by Jeffrey Richter is very good. In short, add the library's as embedded resources and add a callback before anything else. Here is a version of the code (found in the comments of his page) that I put at the start of Main method for a console app (just make sure that any calls that use the library's are in a different method to Main).

    AppDomain.CurrentDomain.AssemblyResolve += (sender, bargs) =>
            {
                String dllName = new AssemblyName(bargs.Name).Name + ".dll";
                var assem = Assembly.GetExecutingAssembly();
                String resourceName = assem.GetManifestResourceNames().FirstOrDefault(rn => rn.EndsWith(dllName));
                if (resourceName == null) return null; // Not found, maybe another handler will find it
                using (var stream = assem.GetManifestResourceStream(resourceName))
                {
                    Byte[] assemblyData = new Byte[stream.Length];
                    stream.Read(assemblyData, 0, assemblyData.Length);
                    return Assembly.Load(assemblyData);
                }
            };
    
    0 讨论(0)
  • 2020-11-21 07:32

    The following method DO NOT use external tools and AUTOMATICALLY include all needed DLL (no manual action required, everything done at compilation)

    I read a lot of answer here saying to use ILMerge, ILRepack or Jeffrey Ritcher method but none of that worked with WPF applications nor was easy to use.

    When you have a lot of DLL it can be hard to manually include the one you need in your exe. The best method i found was explained by Wegged here on StackOverflow

    Copy pasted his answer here for clarity (all credit to Wegged)


    1) Add this to your .csproj file:

    <Target Name="AfterResolveReferences">
      <ItemGroup>
        <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
          <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
        </EmbeddedResource>
      </ItemGroup>
    </Target>
    

    2) Make your Main Program.cs look like this:

    [STAThreadAttribute]
    public static void Main()
    {
        AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
        App.Main();
    }
    

    3) Add the OnResolveAssembly method:

    private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
    {
        Assembly executingAssembly = Assembly.GetExecutingAssembly();
        AssemblyName assemblyName = new AssemblyName(args.Name);
    
        var path = assemblyName.Name + ".dll";
        if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false) path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
    
        using (Stream stream = executingAssembly.GetManifestResourceStream(path))
        {
            if (stream == null) return null;
    
            var assemblyRawBytes = new byte[stream.Length];
            stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
            return Assembly.Load(assemblyRawBytes);
        }
    }
    
    0 讨论(0)
  • 2020-11-21 07:44

    Another product that can handle this elegantly is SmartAssembly, at SmartAssembly.com. This product will, in addition to merging all dependencies into a single DLL, (optionally) obfuscate your code, remove extra meta-data to reduce the resulting file size, and can also actually optimize the IL to increase runtime performance.

    There is also some kind of global exception handling/reporting feature it adds to your software (if desired) that could be useful. I believe it also has a command-line API so you can make it part of your build process.

    0 讨论(0)
提交回复
热议问题