- Joined
- Mar 25, 2012
- Messages
- 909
- Reaction score
- 464
Merging .NET components
Version: 1.1
Author: שเ๒єtгเ๒є (vibetribe)
Tested with: Visual Studio 2012
Language: C# (and VB.NET)
Table of contents
1. Introduction
1.1 Problem analysis
1.2 Most used unsuitable solutions
1.3 Specific example
2. Embedding components while building
2.1 Reference components
2.2 Embedding referenced components
2.3 Resolve assemblies
2.4 Call assembly resolve event before component usage
3. Update embedded components automatically
3.1 Update components from own solution
3.2 Update components from extern solution
4. Access embedded components over embedded components
4.1 Cross-references
4.2 Cross-forwarding
4.3 Renounce useless merging
5. Duplicate component behavior
5.1 Storage behavior
5.2 Loading behavior
1. Introduction
I'm happy to introduce a detailed tutorial about .NET component merging here at RaGEZONE. This tutorial took me about 3 days of researching work through the deepest edges of the internet, and it was composed by many different pieces of information.
This tutorial will not only explain how to get this procedure done, it will even explain what happens behind the scene and what's the advantages and disadvantages of the different layouts as well as alternatives.
Please keep in mind, this is addressed to people who want to learn it, not just use it.
For all VB.NET users. This tutorial works for you as well as for C# users. However, all provided codes and templates are shown in C#. To get around this, use any C#-VB.NET converter, since both can be translated 1:1.
1.1 Problem analysis
In the development progress of .NET components and .NET applications many people lead to solutions with many projects and many external .NET components that will fill the output directory with several DLL files by using many references on build time. To avoid such file salads to customers/clients we are trying to find an effective way to merge DLL files in EXE files. We also need a way to merge DLL files in other DLL files in case of providing a DLL component for the customer/client he/she can work with. Any produced merged components should still stay fully compatible with other .NET components, so have no wrapper that changes the MSIL to something incompatible, no matter it will just be a class library, console application, windows forms application or WPF application.
This tutorial leads to a solution that will make all this done without forcing to repeat any manual steps when updating your code. So it will allow you to implement it on your personal visual studio solution that contains your projects, and it will execute such steps for you automatically when building your application on debug or release build.
1.2 Most used unsuitable solutions
Most people counting for after-solutions. Meaning, you take your components and run any program over it to merge them. The most known tool is might ILmerge, but even .NetZ is common.
Problem on both solutions is the incompatibility to WPF applications, they will not start after merging, because they are unable to find the XAML resources in the merged components. We will avoid such WPF problems, even when the embedded assemblies contain other WPF components such as UserControls or CustomControls.
Also .NetZ will use an own wrapper that makes your components inconsistent to other .NET components until they are packed the same way.
A statement of the programming author of ILmerge includes that he would never have programmed ILmerge when he had heard about the better way to merge components within Visual Studio.
1.3 Specific example
To explain how merged components can be merged another time this tutorial will orient on the following solution structure:
- to reference MergeExample.ClassLibrary_A from MergeExample.ClassLibrary_B and supply MergeExample.ClassLibrary_A.dll into MergeExample.ClassLibrary_B.dll
- to reference the merged MergeExample.ClassLibrary_B from MergeExample.WindowsFormsApplication_C and MergeExample.WpfApplication_C, so supply MergeExample.ClassLibrary_B.dll (that already contains MergeExample.ClassLibrary_A.dll) into MergeExample.WindowsFormsApplication_C.exe and MergeExample.WpfApplication_C.exe while having the namespace MergeExample.ClassLibrary_B visible in it.
- to update embedded components when changing it's source, automatically
- to make the namespace MergeExample.ClassLibrary_A visible in MergeExample.WindowsFormsApplication_C and MergeExample.WpfApplication_C, additionally
Using this on windows services is easy, too. But I bet you will get it yourself when you are able to program windows services, already.
2. Embedding components while building
2.1 Reference components
To be able to use references at all, you have to add the references in your solution browser by right clicking on the reference folder of your project and adding your components you want to use in the specific namespaces.
To continue you need the first built component you want to merge into another component. In this case we need the produced DLL of MergeExample.ClassLibrary_A. So right click on the project in your solution browser and select Create.
2.2 Embedding referenced components
Now create a directory named Internal Libraries in your project that will have embedded components. It is very important to name it only that way, because it will be required for later steps to update it, automatically.
2.3 Resolve assemblies
However, when executing your program or referencing a merged DLL component you will lead into a System.IO.FileNotFoundException because the framework is unable to resolve the assembly, because it's simply missed in the components directory. In any case the component will search in the directory for the missing component as first. So even when we get a work-around people can place compatible DLLs in the folder to bypass the internal loading of the components.
In every project that contain Internal Libraries you need a ModuleInitializer class that will help you to resolve the missing assemblies, the class name is important here, too.
For your easiness I created a template file for you, you can easily put into %userprofile%\Documents\Visual Studio 2012\Templates\ItemTemplates\Visual C#\:
Download:
You must be registered to see links
(Do not extract this file, Visual Studio expects ZIP files in the above directory.)
After adding the file, please restart Visual Studio.
Source Code:
[paste]ZhNnyMfW[/paste]
As you can see ModuleInitializer.Run() will subscripe to the resolve event of current domain. This event will be called by .NET on execution time after the failing to load assemblies into the current domain on runtime. This is exactly what happens when the specific assemblies are missed in the directory.
2.4 Call assembly resolve event before component usage
To be sure you subscribe the resolve event early enough there are specific solution cases.
On applications of any kind you just have to call ModuleInitializer.Run() on the static constructor in your entry class of your program. Placing it anywhere else like the Main function will be too late.
PHP:
static class Program
{
static Program()
{
ModuleInitializer.Run();
}
}
PHP:
public partial class App : Application
{
static App()
{
ModuleInitializer.Run();
}
}
On DLL components you will lead into a big problem, because the .NET framework does not provide any way to start any code before loading associated components on run time.
For this you have to inject your DLL-components that contain any Internal Libraries on the post build event (after build). This behavior can be done with the InjectModuleInitializer.exe.
Download:
You must be registered to see links
Virus Total:
You must be registered to see links
Place this EXE file into the root folder of your solution.
Code:
..\..\..\InjectModuleInitializer.exe $(TargetName)$(TargetExt)
Now your solution projects are able to build and run successfully.
3. Update embedded components automatically
It is usual to be forced to update references of third party components, manually. However, in this case even your own embedded components will need to be re-added when changing it's source and building again. So in our example I would be forced to re-add MergeExample.ClassLibrary_A.dll in the Internal Libraries folder of MergeExample.ClassLibrary_B when changing and building the source of MergeExample.ClassLibrary_A.
To automate this process I've wrote a tool called UpdateInternalLibraries.exe
Download:
You must be registered to see links
Virus Total:
You must be registered to see links
Source Code:
[paste]7hF9TX8R[/paste]
3.1 Update components from own solution
In order to update the components into your Internal Libraries folder before building your project open it's project properties and navigate to the Build Event tab.
Code:
..\..\..\InternalLibrariesUpdater.exe
3.2 Update components from extern solution
There is a case you embedded a component from another solution on your drive. In this case you can provide an additional parameter to search in another solution directory for the build components you want to update into your project that merges those. This parameter should navigate to the other solution.
Code:
..\..\..\InternalLibrariesUpdater.exe ..\..\..\..\OtherSolutionName
Please keep in mind to use both methods (so two calls to InternalLibrariesUpdater.exe) when having intern and extern solution components embedded into your project.
4. Access embedded components over embedded components
4.1 Cross-references
The classic work-around is simply as it is to add a reference to LibA in AppC. This never leat to any problems due the fact LibB has a reference to LibA, so LibA.dll was in the same directory in any case.
When using embedded components in LibB and AppC this way would produce a side effect that is very controversial in my eyes: It would store the assembly LibA two times in AppC.
By using huge amounts of embedded duplicated components in many hierarchic project levels this can increase the final application size a lot.
4.2 Cross-forwarding
You can write an interface (not the typically interface in Visual Studio) to allow AppC to access all needed components in LibA by providing inherted classes of LibA in LibB as well as providing properties and methods in LibB that access code in LibA and forward your parameters as well as return values.
4.3 Renounce useless merging
Unless you need LibB as single DLL file that contains it's associated components, do not do it. The above examples are not perfect, it would be more recommend to let only the final application merge all components instead.
5. Duplicate component behaviour
5.1 Storage behavior
The above example will lead your application AppC.exe to have the following assemblies stored in:
- 1 x AppC
- 1 x LibB
- 2 x LibA
- 2 x LibX (1 x v1.0.0.0 and 1 x v2.0.0.1)
5.2 Loading behavior
To your luck the above example will not force your application to load LibA two times into your memory on run time. But this seemingly positive effect got a bad shadow side: Your application will only load one assembly of both LibX assemblies into your memory. It will load the assembly LibX v2.0.0.1 into the memory on run time due the fact it's embedded on a higher stage in the hierarical tree. The version will not affect this decision, because it's simply load as first in the program code. If you would switch both assemblies it would prefer the older LibX in this case. This problem will force LibC to use a wrong version of LibX, so it will throw you an exception on conflicts when accessing LibX from LibC.
The conclusion is to avoid version conflicts at all. Even using extern alias key words or trying to change the AppDomain of the loading components will not help you there.
-----
I hope this tutorial is useful for all our .NET coders here. Feel free to ask questions and leave me a feedback.
Last edited: