664

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?

Normally, I'm cool with just leaving the DLLs outside and having the setup program handle everything, but there have been a couple of people at work who have asked me this and I honestly don't know.

cs95
  • 274,032
  • 76
  • 480
  • 537
Merus
  • 8,236
  • 5
  • 26
  • 41
  • 2
    It is possible, but you will end up with large executable (Base64 will be used to encode your dll). – Paweł Dyda Oct 28 '10 at 13:56
  • 2
    @PawełDyda: You can embed raw binary data into a PE image (see [RCDATA](https://msdn.microsoft.com/en-us/library/windows/desktop/aa381039.aspx)). No transformation required (or recommended). – IInspectable Feb 26 '17 at 19:53
  • Besides [ILMerge](http://research.microsoft.com/~mbarnett/ILMerge.aspx), if you don't want to bother with command line switches, I really recommend [ILMerge-Gui](http://ilmergegui.codeplex.com/). It's an open source project, really good! – tyron Dec 19 '12 at 12:45
  • I would recommend you check out the .NETZ utility, which also compresses the assembly with a scheme of your choice: [http://madebits.com/netz/help.php#single](http://madebits.com/netz/help.php#single) – Nathan Baulch Oct 09 '08 at 23:28

16 Answers16

793

I highly recommend to use Costura.Fody - by far the best and easiest way to embed resources in your assembly. It's available as NuGet package.

Install-Package Costura.Fody

After adding it to the project, it will automatically embed all references that are copied to the output directory into your main assembly. You might want to clean the embedded files by adding a target to your project:

Install-CleanReferencesTarget

You'll also be able to specify whether to include the pdb's, exclude certain assemblies, or extracting the assemblies on the fly. As far as I know, also unmanaged assemblies are supported.

Update

Currently, some people are trying to add support for DNX.

Update 2

For the lastest Fody version, you will need to have MSBuild 16 (so Visual Studio 2019). Fody version 4.2.1 will do MSBuild 15. (reference: Fody is only supported on MSBuild 16 and above. Current version: 15)

Marc Giroux
  • 168
  • 1
  • 7
  • 18
Matthias
  • 14,822
  • 5
  • 36
  • 78
  • 82
    Thank you for this awesome suggestion. Install the package and you're done. It even compresses assemblies by default. – Daniel Dec 19 '13 at 15:40
  • 10
    Hate to be a 'me too', but me too - this saved me a lot of headache! Thank you for the recommendation! This enabled me to package everything I need to redistribute into a single exe and it's now smaller than the original exe and dlls were combined... I've only been using this for a few days, so I can't say that I've put it through its paces, but barring anything bad popping up, I can see this becoming a regular tool in my toolbox. It just works! – mattezell Jun 12 '14 at 19:27
  • 20
    It's cool. But there is a disadvantage: assembly generated on Windows is no longer binary compatible with mono Linux. That means, you cannot deploy the assembly onto Linux mono directly. – Tyler Long Jun 17 '14 at 12:40
  • 1
    Costura does not seem to have hooks for auto-updating from the web. See my question: http://stackoverflow.com/questions/28323709/fody-and-dynamic-dependency-update-assemblyresolve-event – Pompair Feb 04 '15 at 14:55
  • 2
    Creating a clean output directory There is also a powershell cmdlet to install this target into your project automatically. In the Package Manager Console type: PM> Install-CleanReferencesTarget – Siva Jul 23 '15 at 01:16
  • 2
    It worked perfect with Visual Studio 2013 but i doesn't work anymore with VS 2015 (Enterprise) - Anyone with the same problem!?!? – CeOnSql Jul 28 '15 at 13:47
  • @CeOnSql I'm having the same problem on Windows 8.1 and 10 with VS 2015 Community. If anyone has a solution, let me know! – alexyorke Aug 03 '15 at 19:34
  • @alexy13: Since I got so much reputation for this post, I'm feeling responsible for keeping it up-to-date. Unfortunately, I cannot test this right now. I would really appreciate if one of you would create an issue here https://github.com/Fody/Costura/issues. – Matthias Aug 03 '15 at 20:55
  • @Siva: Thanks for your hint. Added it to the post. – Matthias Aug 05 '15 at 22:57
  • Can I included text file in exe ?? I have some native dll and txt file too. Can I make only one exe file using Costura.Fody?? – Md. Yusuf Feb 08 '16 at 12:12
  • 1
    I've found these two: https://exepack.codeplex.com/ AND https://visualstudiogallery.msdn.microsoft.com/a7196a81-67fc-4a26-a88a-b68ef31c2266 – zero.zero.seven Mar 27 '16 at 19:29
  • @Ciprian actually i did not use costura. – Md. Yusuf Oct 10 '16 at 06:00
  • Would anyone like to provide a sample on how you merge it obfuscatedly? I mean the .exe is obfuscated before merging. – newbieguy Apr 01 '17 at 00:25
  • Just tried with VS 2015 R3 and worked a treat. 40% total file size reduction to boot. Shame the NuGet name is so difficult to remember (would never have found it if not for this answer). – Gone Coding Apr 26 '17 at 10:40
  • Just a head's up..I had major problems getting Costura.Fody installed using VS 2013 all updates. Apparently the latest build 1.4.1 isn't working with 2013. I successfully installed 1.4 from the nuget console but had compiler errors. I ended up manually adding them as project resources as the Fody approach in the end wasted my time. I might upgrade once I'm done with my project because the simplicity and compression sounds AWESOME – waltmagic May 18 '17 at 20:14
  • 1
    By the by... I had a project using Fody, in Visual Studio 2012. Everything worked BEAUTIFULLY! Now I upgraded to Visual Studio 2017, and Fody is completely pooched; Doesn't work at all. – eidylon May 24 '17 at 21:16
  • @CeOnSql, and alexy13 works fine in VS2015 _update 3, 14.0.254431.01_ – Mehdi Dehghani Sep 11 '17 at 18:31
  • Lifesaver. To folks who don't know how to actually install Costura.Fody, just go in VS, under Tools->NuGet Package Manager->Package Manager Console. There you go for "Install-Package Costura.Fody" command. Re-build your app and you're good. Simple as that! Not being snooty here, I had to figure out myself too. – Eldoïr Sep 22 '17 at 12:51
  • it does not embed "SQLite.Interop.dll" of System.Data.SQLite.Core library – Gene R Feb 12 '18 at 14:52
  • 8
    This is lovely! If you're using vs2018 don't forget the FodyWeavers.xml file to be located at the root of your project. – Alan Deep Apr 14 '18 at 05:15
  • This is really perfect in 2018 as well! :) Very helpful! – rigerta Jun 07 '18 at 09:35
  • 5
    As a supplement to the last comment: add the FodyWeavers.xml with the following content to your project: – HHenn Aug 29 '18 at 14:13
  • Still works in 2018, but you need to follow guide from readme.md here https://github.com/Fody/Costura – super sahar Nov 27 '18 at 09:10
  • it is work with references added from NUGETPackages. But not working if I add solution of existing project as reference to it. Can anyone help why is this? – nsds Feb 07 '19 at 07:51
  • For those getting an error with MSBuild: https://stackoverflow.com/a/55843973/6674014 – DCOPTimDowd May 06 '19 at 20:09
  • Not a solution for me - Symantec labelled this Nuget package a virus and auto-deleted it – DAG Jan 10 '20 at 15:41
  • 3
    The Package Manager Console command `Install-CleanReferencesTarget` is no longer valid and will fail. It's automated in current versions. Also for Visual Studio 2017 (using MSBuild 15) install the Nugets `Fody 4.2.1` and `Costura.Fody 3.3.3` for a successful compile. – Arvo Bowen Feb 21 '20 at 18:27
95

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/

M. Wiśnicki
  • 5,749
  • 3
  • 20
  • 27
Lars Holm Jensen
  • 1,524
  • 11
  • 13
  • 6
    You can have this behaviour out of the box. Check out my answer http://stackoverflow.com/a/20306095/568266 – Matthias Nov 30 '13 at 21:57
  • 4
    Also important to note an INCREDIBLY useful comment on your blog from AshRowe: if you have a custom theme installed, it will try to resolve the PresentationFramework.Theme assembly which crashes and burns! As per AshRowe's suggestion, you can simply check if the dllName contains PresentationFramework like so: if (dllName.ToLower().Contains("presentationframework")) return null; – YasharBahman Jan 31 '14 at 06:45
  • 4
    Two comments on this. One: you should check if `bytes` is null, and if so, return null there. It's possible the dll is _not_ in the resources, after all. Two: This only works if that class itself does not have a "using" for anything from that assembly. For command line tools, I had to move my actual program code to a new file, and make a small new main program that just does this and then calls the original main in the old class. – Nyerguds May 26 '14 at 07:41
  • 1
    On a related note, this can be used to compile a solution with 2 projects into one exe file... all you need to do is make the pre-build scripts of the embedding project overwrite the dll used as resource with the built dll of the embedded project, and make the post-build scripts of the embedding project remove the dlls from the solution output folder. – Nyerguds May 26 '14 at 08:26
  • 1
    Another useful note: if you use `byte[] bytes = rm.GetObject(dllName) as byte[];` you can't get cast errors there, again if the system somehow accidentally fetched an existing resource of a different type. – Nyerguds Jun 04 '14 at 07:06
  • 1
    Where can I find `App.xaml.cs` or similar in Winforms? Is it a file I need to create? – Dan W Jun 29 '15 at 09:40
  • 3
    Okay so your answer is meant for WPF. I got it to work with Winforms. After adding the resource as you said, simply put the `AppDomain.CurrentDomain.AssemblyResolve +=new ResolveEventHandler(CurrentDomain_AssemblyResolve);` line before the `InitializeComponent();` line in the Form constructor. Then put the entire `System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)` method anywhere. Compile and run. That's it. It makes your solution even easier than the highest scoring answer, as there's no third party utility to download. – Dan W Jun 29 '15 at 10:33
  • @DanW: Normal form applications just have a program.cs which creates the actual form object and launches it. You can just put it in there, before the creation of the form object. There should be nothing else in there anyway. Your solution may not work since the actual class containing this code can't use the assembly stuff loaded this way, since assembly resolve is done before the constructor is executed. – Nyerguds Aug 24 '15 at 07:24
  • 2
    The upside of this approach is that it doesn't rely in installing any external libs to achieve the desired functionality. The downside of this approach is that it's useful only when it comes to managed dlls - interop dlls (at least as far as my testing goes) do not fire the assemblyresolve event and even if they did the Assembly.Load() doesn't achieve the desirable effect down the road. http://stackoverflow.com/questions/13113131/using-assemblyresolve-to-handle-missing-assemblies-in-c-sharp Just my 2c on the matter – XDS Aug 28 '15 at 11:53
  • 4
    Just in case anyone runs into my issue: if the `.dll` name contains any hyphens (i.e. `twenty-two.dll`), those will also be replaced with an underscore (i.e. `twenty_two.dll`). You can change this line of code to this: `dllName = dllName.Replace(".", "_").Replace("-", "_");` – Micah Vertal Jul 14 '16 at 01:03
  • When I do this my solution it wont compile. It gives an error anywhere I've used a class from the dll – Notts90 supports Monica Dec 03 '19 at 10:08
  • @Notts90 You still have to reference the library to compile the solution – Lars Holm Jensen Dec 06 '19 at 13:45
89

If they're actually managed assemblies, you can use ILMerge. For native DLLs, you'll have a bit more work to do.

See also: How can a C++ windows dll be merged into a C# application exe?

Lucas Carneiro
  • 400
  • 4
  • 11
Shog9
  • 146,212
  • 34
  • 221
  • 231
  • I am interested in Native DLL merge, is there any materials? – baye Mar 05 '09 at 02:45
  • 5
    See also: http://stackoverflow.com/questions/108971/using-side-by-side-assemblies-to-load-the-x64-or-x32-version-of-a-dll/156024#156024 – Milan Gardian Apr 09 '12 at 20:56
  • @BaiyanHuang look at https://github.com/boxedapp/bxilmerge, the idea is to make "ILMerge" for native Dlls. – Artem Razin Feb 17 '20 at 15:47
  • VB NET developers like me don't be scared with that `C++` at the link. ILMerge also works very easily for VB NET. See here [https://github.com/dotnet/ILMerge](https://github.com/dotnet/ILMerge). Thanks @Shog9 – Ivan Ferrer Villa May 25 '20 at 08:55
26

Yes, it is possible to merge .NET executables with libraries. There are multiple tools available to get the job done:

  • ILMerge is a utility that can be used to merge multiple .NET assemblies into a single assembly.
  • Mono mkbundle, packages an exe and all assemblies with libmono into a single binary package.
  • IL-Repack is a FLOSS alterantive to ILMerge, with some additional features.

In addition this can be combined with the Mono Linker, which does remove unused code and therefor makes the resulting assembly smaller.

Another possibility is to use .NETZ, which does not only allow compressing of an assembly, but also can pack the dlls straight into the exe. The difference to the above mentioned solutions is that .NETZ does not merge them, they stay separate assemblies but are packed into one package.

.NETZ is a open source tool that compresses and packs the Microsoft .NET Framework executable (EXE, DLL) files in order to make them smaller.

Bobby
  • 10,998
  • 5
  • 42
  • 67
  • NETZ seems to be gone – Rbjz Jun 10 '16 at 10:20
  • Wow - I thought I finally found it, then I read this comment. It seems to be gone totally. Are there any forks? – Mafii Oct 08 '16 at 16:19
  • Well, it just moved to GitHub and it is no longer linked on the website...so "gone totally" is an overstatement. Most likely it is not supported anymore, but it is still there. I updated the link. – Bobby Oct 08 '16 at 18:55
20

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.

Community
  • 1
  • 1
nawfal
  • 62,042
  • 48
  • 302
  • 339
16

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);
            }
        };
Steve
  • 1,352
  • 1
  • 14
  • 28
  • 1
    Changed it a bit, did the job, tnx buddy! – Sean Ed-Man Nov 21 '14 at 16:00
  • The project https://libz.codeplex.com/ uses this process but it will do some other things too like manage the event handler for you and some special code not to break "[Managed Extensibility Framework Catalogs](https://mef.codeplex.com/wikipage?title=Using%20Catalogs)" (which by it self this process would break) – Scott Chamberlain Feb 23 '15 at 19:53
  • That's great!! Thanks @Steve – Ahmer Afzal Aug 03 '19 at 09:39
14

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>
Community
  • 1
  • 1
Josh
  • 667
  • 1
  • 9
  • 18
  • 1
    The syntax for IL-Repack has changed, check the README.md that is on the linked github repo ( https://github.com/peters/ILRepack.MSBuild.Task ). This way was the only one that worked for me and I was able to use a wildcard to match all of the dlls that I wanted to include. – Seabass77 Jul 26 '19 at 12:29
9

.NET Core 3.0 natively supports compiling to a single .exe

The feature is enabled by the usage of the following property in your project file (.csproj):

    <PropertyGroup>
        <PublishSingleFile>true</PublishSingleFile>
    </PropertyGroup>

This is done without any external tool.

See my answer for this question for further details.

Marcell Toth
  • 2,617
  • 1
  • 15
  • 26
8

You could add the DLLs as embedded resources, and then have your program unpack them into the application directory on startup (after checking to see if they're there already).

Setup files are so easy to make, though, that I don't think this would be worth it.

EDIT: This technique would be easy with .NET assemblies. With non-.NET DLLs it would be a lot more work (you'd have to figure out where to unpack the files and register them and so on).

MusiGenesis
  • 71,592
  • 38
  • 183
  • 324
  • Here you have a great article that explains how to do this: http://www.codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource – bluish Sep 09 '15 at 14:44
8

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.

Joy
  • 179
  • 8
Nathan
  • 12,160
  • 3
  • 25
  • 27
7

Neither the ILMerge approach nor Lars Holm Jensen's handling the AssemblyResolve event will work for a plugin host. Say executable H loads assembly P dynamically and accesses it via interface IP defined in an separate assembly. To embed IP into H one shall need a little modification to Lars's code:

Dictionary<string, Assembly> loaded = new Dictionary<string,Assembly>();
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{   Assembly resAssembly;
    string dllName = args.Name.Contains(",") ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll","");
    dllName = dllName.Replace(".", "_");
    if ( !loaded.ContainsKey( dllName ) )
    {   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);
        resAssembly = System.Reflection.Assembly.Load(bytes);
        loaded.Add(dllName, resAssembly);
    }
    else
    {   resAssembly = loaded[dllName];  }
    return resAssembly;
};  

The trick to handle repeated attempts to resolve the same assembly and return the existing one instead of creating a new instance.

EDIT: Lest it spoil .NET's serialization, make sure to return null for all assemblies not embedded in yours, thereby defaulting to the standard behaviour. You can get a list of these libraries by:

static HashSet<string> IncludedAssemblies = new HashSet<string>();
string[] resources = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames();
for(int i = 0; i < resources.Length; i++)
{   IncludedAssemblies.Add(resources[i]);  }

and just return null if the passed assembly does not belong to IncludedAssemblies .

Ant_222
  • 801
  • 9
  • 17
  • Sorry for posting it as an answer rather than as a comment. I don't have the right to comment others' answers. – Ant_222 Oct 09 '13 at 11:27
5

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);
    }
}
Ludovic Feltz
  • 10,007
  • 3
  • 43
  • 51
  • Can you explain the test on the `CultureInfo`, is there some `en-us` or `fr-fr` subfolder? Is this the `DestinationSubDirectory` ? – Orace Mar 17 '21 at 20:16
3

It may sound simplistic, but WinRar gives the option to compress a bunch of files to a self-extracting executable.
It has lots of configurable options: final icon, extract files to given path, file to execute after extraction, custom logo/texts for popup shown during extraction, no popup window at all, license agreement text, etc.
May be useful in some cases.

Ivan Ferrer Villa
  • 1,960
  • 1
  • 24
  • 21
  • Windows itself has a similar tool called iexpress. [Here's a tutorial](http://www.instructables.com/id/Create-A-Self-Extracting-Archive/) – Ivan Ferrer Villa Jan 20 '15 at 09:22
2

I use the csc.exe compiler called from a .vbs script.

In your xyz.cs script, add the following lines after the directives (my example is for the Renci SSH):

using System;
using Renci;//FOR THE SSH
using System.Net;//FOR THE ADDRESS TRANSLATION
using System.Reflection;//FOR THE Assembly

//+ref>"C:\Program Files (x86)\Microsoft\ILMerge\Renci.SshNet.dll"
//+res>"C:\Program Files (x86)\Microsoft\ILMerge\Renci.SshNet.dll"
//+ico>"C:\Program Files (x86)\Microsoft CAPICOM 2.1.0.2 SDK\Samples\c_sharp\xmldsig\resources\Traffic.ico"

The ref, res and ico tags will be picked up by the .vbs script below to form the csc command.

Then add the assembly resolver caller in the Main:

public static void Main(string[] args)
{
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    .

...and add the resolver itself somewhere in the class:

    static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        String resourceName = new AssemblyName(args.Name).Name + ".dll";

        using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
        {
            Byte[] assemblyData = new Byte[stream.Length];
            stream.Read(assemblyData, 0, assemblyData.Length);
            return Assembly.Load(assemblyData);
        }

    }

I name the vbs script to match the .cs filename (e.g. ssh.vbs looks for ssh.cs); this makes running the script numerous times a lot easier, but if you aren't an idiot like me then a generic script could pick up the target .cs file from a drag-and-drop:

    Dim name_,oShell,fso
    Set oShell = CreateObject("Shell.Application")
    Set fso = CreateObject("Scripting.fileSystemObject")

    'TAKE THE VBS SCRIPT NAME AS THE TARGET FILE NAME
    '################################################
    name_ = Split(wscript.ScriptName, ".")(0)

    'GET THE EXTERNAL DLL's AND ICON NAMES FROM THE .CS FILE
    '#######################################################
    Const OPEN_FILE_FOR_READING = 1
    Set objInputFile = fso.OpenTextFile(name_ & ".cs", 1)

    'READ EVERYTHING INTO AN ARRAY
    '#############################
    inputData = Split(objInputFile.ReadAll, vbNewline)

    For each strData In inputData

        if left(strData,7)="//+ref>" then 
            csc_references = csc_references & " /reference:" &         trim(replace(strData,"//+ref>","")) & " "
        end if

        if left(strData,7)="//+res>" then 
            csc_resources = csc_resources & " /resource:" & trim(replace(strData,"//+res>","")) & " "
        end if

        if left(strData,7)="//+ico>" then 
            csc_icon = " /win32icon:" & trim(replace(strData,"//+ico>","")) & " "
        end if
    Next

    objInputFile.Close


    'COMPILE THE FILE
    '################
    oShell.ShellExecute "c:\windows\microsoft.net\framework\v3.5\csc.exe", "/warn:1 /target:exe " & csc_references & csc_resources & csc_icon & " " & name_ & ".cs", "", "runas", 2


    WScript.Quit(0)
0

It's possible but not all that easy, to create a hybrid native/managed assembly in C#. Were you using C++ instead it'd be a lot easier, as the Visual C++ compiler can create hybrid assemblies as easily as anything else.

Unless you have a strict requirement to produce a hybrid assembly, I'd agree with MusiGenesis that this isn't really worth the trouble to do with C#. If you need to do it, perhaps look at moving to C++/CLI instead.

Chris Charabaruk
  • 4,267
  • 2
  • 26
  • 57
0

Generally you would need some form of post build tool to perform an assembly merge like you are describing. There is a free tool called Eazfuscator (eazfuscator.blogspot.com/) which is designed for bytecode mangling that also handles assembly merging. You can add this into a post build command line with Visual Studio to merge your assemblies, but your mileage will vary due to issues that will arise in any non trival assembly merging scenarios.

You could also check to see if the build make untility NANT has the ability to merge assemblies after building, but I am not familiar enough with NANT myself to say whether the functionality is built in or not.

There are also many many Visual Studio plugins that will perform assembly merging as part of building the application.

Alternatively if you don't need this to be done automatically, there are a number of tools like ILMerge that will merge .net assemblies into a single file.

The biggest issue I've had with merging assemblies is if they use any similar namespaces. Or worse, reference different versions of the same dll (my problems were generally with the NUnit dll files).

wllmsaccnt
  • 1,610
  • 1
  • 15
  • 26