C# Reflection - determine location of dependencies at runtime - c#

On the project I'm currently working on we've undertaken a massive piece of work to style the forms and make them more consistent with the business rules. This has involved things such as padding of forms, changing labels, changing column headers etc - loads of stuff basically.
I've been asked to look into the feasibility of producing a tool to compare our original forms vs. the new ones to see what has been changed. I've gone down the route of using reflection for this - getting the original DLLs and loading the forms in turn and comparing the controls (I couldn't think of an easier way to do it, as a lot of our forms have other items "injected" at run time, so a comparison of the designer files wouldn't work.
In my project I've created two directories called "OriginalAssemblies" and "CurrentAssemblies" and within each of these there is a dependencies directory, which contains all the assemblies on which the forms are reliant. These files will, in the main be different between Original and Current.
My first method reads in the assemblies, based on a directory and stores then in a list - there is one for original and one for the current:
private static void LoadInAssemblies(string sourceDir, List<Assembly> assemblyList)
{
DirectoryInfo dir = new DirectoryInfo(sourceDir);
foreach (FileInfo currentFile in dir.GetFiles().Where(f => f.Name.Contains("dll")))
{
assemblyList.Add(Assembly.LoadFile(currentFile.FullName));
}
}
This appears to work fine in that it loads the DLLs, which contains the forms. Next, I have a method which loads in the distinct forms and processes them. This runs, but it loads in the dependencies from the same directory each time
foreach (Assembly current in originalAssemblies)
{
var items = current.GetTypes().Where(t => t.IsSubclassOf(typeof(Form)) && !t.Name.Contains("Base")).ToList();
foreach (var newForm in items)
{
Object originalForm = Activator.CreateInstance(newForm);
// get the associated assembly from the new form
Object currentForm = Activator.CreateInstance (currentAssemblies.SelectMany(a => a.GetTypes()).Where (t => t.Name == newForm.Name).First());
((Form)originalForm).ShowDialog();
((Form)currentForm).ShowDialog();
}
}
After a bit of research I found reference to probing in the app.config, so set it as follows:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="OriginalAssemblies/Dependencies"/>
</assemblyBinding>
</runtime>
Now I realise that I can separate directories and have Current and Original directories, but in this case it won't help because the DLLs in the dependencies folder will be identical - just different assembly versions.
My question is, based on the code above is there any way I can tell the GetType() command when creating the object currentForm to use the correct directory, which in this case would be CurrentAssemblies?

I've had a go with loading multiple versions of the same kind of dll. This is what I came up with:
You'll need to use the AssemblyResolve event on two AppDomain to handle the redirect of the referenced dll files in your project automatically (here my "current" dlls were in "bin" and the "original" were in "bin/original"):
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
// Path for to "Current" dlls.
string newPath = Path.Combine(Path.GetDirectoryName(Assembly.GetCallingAssembly().Location),
"bin", new AssemblyName(args.Name).Name + ".dll");
if (File.Exists(newPath))
return Assembly.LoadFrom(newPath);
return args.RequestingAssembly;
}
private static Assembly CurrentDomain_AssemblyResolve_Original(object sender, ResolveEventArgs args)
{
// Path for the "Original" dlls.
string newPath = Path.Combine(Path.GetDirectoryName(Assembly.GetCallingAssembly().Location),
"bin", "original", new AssemblyName(args.Name).Name + ".dll");
if (File.Exists(newPath))
return Assembly.LoadFrom(newPath);
return args.RequestingAssembly;
}
IMPORTANT
Make sure to put the code below as soon as possible (in the very first constructor called) or the event subscriptions won't be called!!:
private static readonly AppDomain OriginalAppDomain;
// Here, "Program" is my first class constructor called on startup.
static Program()
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_AssemblyResolve;
// Second app domain for old DLLs.
OriginalAppDomain = AppDomain.CreateDomain("Original DLLs", new Evidence());
OriginalAppDomain.AssemblyResolve += CurrentDomain_AssemblyResolve_Original;
OriginalAppDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_AssemblyResolve_Original;
}
Now you can test both dlls in one go like this:
// Runs on current AppDomain.
new SpecialForm().ShowDialog();
// Runs on OriginalAppDomain (created above).
OriginalAppDomain.DoCallBack(() => new SpecialForm().ShowDialog());
Possible improvements
Changing the AssemblyResolve methods with checks on version instead of directory would be possible as well. So you'll have more control on which Assembly will be loaded.

Your problem is that when an assembly loaded, it will stay in memory and subsequent calls to assemblies that require that assembly will automatically use it. This is why AppDomain.AssemblyResolve will not help you and cause more trouble.
My approach would be like this
Create a tool that creates your forms and dumps them to text files. Then this tool in two times, first in original, second in modified folder. Compare text files using any text compare tool you like, or compare yourself if you feel like it.

Related

How to dynamically load and unload (reload) a .dll assembly

I'm developing a module for an external application, which is a dll that is loaded.
However in order to develop, you have to restart the application to see the results of your code.
We have built a piece of code that loads a dll dynamically from a startassembly:
startassembly
var dllfile = findHighestAssembly(); // this works but omitted for clarity
Assembly asm = Assembly.LoadFrom(dllFile);
Type type = asm.GetType("Test.Program");
MethodInfo methodInfo = type.GetMethod("Run");
object[] parametersArray = new object[] { };
var result = methodInfo.Invoke(methodInfo, parametersArray);
Effectively we have a solution with a startassembly which will be static and a test assembly which will be invoked dynamically, which allows us to swap the assembly during runtime.
The problem
This piece of code will load a new dll every time and search for the highest version at the end of the assembly name. e.g. test02.dll will be loaded instead of test01.dll, because the application locks both startassemly.dll as well as test01.dll. Now we have to edit the properties > assembly name all the time.
I want to build a new dll while the main application still runs. However for now I get the message
The process cannot access the file test.dll because it is being used
by another process
I have read that you can unload a .dll using AppDomains however the problem is that I don't know how to properly unload an AppDomain and where to do this.
The goal is to have to reload the new test.dll everytime the window is re-opened (by a button click from the main application).
You cannot unload a single assembly, but you can unload an Appdomain. This means you need to create an app domain and load the assembly in the App domain.
Exmaple:
var appDomain = AppDomain.CreateDomain("MyAppDomain", null, new AppDomainSetup
{
ApplicationName = "MyAppDomain",
ShadowCopyFiles = "true",
PrivateBinPath = "MyAppDomainBin",
});
ShadowCopyFiles property will cause the .NET runtime to copy dlls in "MyAppDomainBin" folder to a cache location so as not to lock the files in that path. Instead the cached files are locked. For more information refer to article about Shadow Copying Assemblies
Now let's say you have an class you want to use in the assembly you want to unload. In your main app domain you call CreateInstanceAndUnwrap to get an instance of the object
_appDomain.CreateInstanceAndUnwrap("MyAssemblyName", "MyNameSpace.MyClass");
However, and this is very important, "Unwrap" part of CreateInstanceAndUnwrap will cause the assembly to be loaded in your main app domain if your class does not inherit from MarshalByRefObject. So basically you achieved nothing by creating an app domain.
To solve this problem, create a 3rd Assembly containing an Interface that is implemented by your class.
For example:
public interface IMyInterface
{
void DoSomething();
}
Then add reference to the assembly containing the interface in both your main application and your dynamically loaded assembly project. And have your class implement the interface, and inherit from MarshalByRefObject. Example:
public class MyClass : MarshalByRefObject, IMyInterface
{
public void DoSomething()
{
Console.WriteLine("Doing something.");
}
}
And to get a reference to your object:
var myObj = (IMyInterface)_appDomain.CreateInstanceAndUnwrap("MyAssemblyName", "MyNameSpace.MyClass");
Now you can call methods on your object, and .NET Runtime will use Remoting to forward the call to the other domain. It will use Serialization to serialize the parameters and return values to and from both domains. So make sure your classes used in parameters and return values are marked with [Serializable] Attribute. Or they can inherit from MarshalByRefObject in which case the you are passing a reference cross domains.
To have your application monitor changes to the folder, you can setup a FileSystemWatcher to monitor changes to the folder "MyAppDomainBin"
var watcher = new FileSystemWatcher(Path.GetFullPath(Path.Combine(".", "MyAppDomainBin")))
{
NotifyFilter = NotifyFilters.LastWrite,
};
watcher.EnableRaisingEvents = true;
watcher.Changed += Folder_Changed;
And in the Folder_Changed handler unload the appdomain and reload it again
private static async void Watcher_Changed(object sender, FileSystemEventArgs e)
{
Console.WriteLine("Folder changed");
AppDomain.Unload(_appDomain);
_appDomain = AppDomain.CreateDomain("MyAppDomain", null, new AppDomainSetup
{
ApplicationName = "MyAppDomain",
ShadowCopyFiles = "true",
PrivateBinPath = "MyAppDomainBin",
});
}
Then when you replace your DLL, in "MyAppDomainBin" folder, your application domain will be unloaded, and a new one will be created. Your old object references will be invalid (since they reference objects in an unloaded app domain), and you will need to create new ones.
A final note: AppDomains and .NET Remoting are not supported in .NET Core or future versions of .NET (.NET 5+). In those version, separation is achieved by creating separate processes instead of app domains. And using some sort of messaging library to communicate between processes.
Not the way forward in .NET Core 3 and .NET 5+
Some of the answers here assume working with .NET Framework. In .NET Core 3 and .NET 5+, the correct way to load assemblies (with ability to unload them) in the same process is with AssemblyLoadContext. Using AppDomain as a way to isolate assemblies is strictly for .NET Framework.
.NET Core 3 and 5+, give you two possible ways to load dynamic assemblies (and potentially unload):
Load another process and load your dynamic assemblies there. Then use an IPC messaging system of your choosing to send messages between the processes.
Use AssemblyLoadContext to load them in the same process. Note that the scope does NOT provide any kind of security isolation or boundaries within the process. In other words, code loaded in a separate context is still able to invoke other code in other contexts within the same process. If you want to isolate the code because you expect to be loading assemblies that you can't fully trust, then you need to load it in a completely separate process and rely on IPC.
An article explaining AssemblyLoadContext is here.
Plugin unloadability discussed here.
Many people who want to dynamically load DLLs are interested in the Plugin pattern. The MSDN actually covers this particular implementation here:
https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support
2021-9-12 UPDATE
Off-the-Shelf Library for Plugins
I use the following library for plugin loading. It has worked extremely well for me:
https://github.com/natemcmaster/DotNetCorePlugins
what you're trying to do in the code you posted is unload the default app domain which your program will run in if another isn't specified. What you're probably wanting is to load a new app domain, load the assembly into that new app domain, and then unloaded the new app domain when the user destroys the page.
https://learn.microsoft.com/en-us/dotnet/api/system.appdomain?view=netframework-4.7
the reference page above should give you a working example of all of this.
Here is an example for loading and unloading an AppDomain.
In my example I have 2 Dll's: DynDll.dll and DynDll1.dll.
Both Dll's have the same class DynDll.Class and a method Run (MarshalByRefObject is required):
public class Class : MarshalByRefObject
{
public int Run()
{
return 1; //DynDll1 return 2
}
}
Now you can create a dynamic AppDomain and load a Assembly:
AppDomain loDynamicDomain = null;
try
{
//FullPath to the Assembly
string lsAssemblyPath = string.Empty;
if (this.mbLoad1)
lsAssemblyPath = Path.Combine(Application.StartupPath, "DynDll1.dll");
else
lsAssemblyPath = Path.Combine(Application.StartupPath, "DynDll.dll");
this.mbLoad1 = !this.mbLoad1;
//Create a new Domain
loDynamicDomain = AppDomain.CreateDomain("DynamicDomain");
//Load an Assembly and create an instance DynDll.Class
//CreateInstanceFromAndUnwrap needs the FullPath to your Assembly
object loDynClass = loDynamicDomain.CreateInstanceFromAndUnwrap(lsAssemblyPath, "DynDll.Class");
//Methode Info Run
MethodInfo loMethodInfo = loDynClass.GetType().GetMethod("Run");
//Call Run from the instance
int lnNumber = (int)loMethodInfo.Invoke(loDynClass, new object[] { });
Console.WriteLine(lnNumber.ToString());
}
finally
{
if (loDynamicDomain != null)
AppDomain.Unload(loDynamicDomain);
}
Here is an idea, instead of loading the DDL directly (as is), let the application rename it, then load the renamed ddl (e.g. test01_active.dll). Then, just check for the original file (test01.dll) before loading the assembly and if exists, just delete the current one(test01_active.dll) and then rename the updated version then reload it, and so on.
Here is a code shows the idea :
const string assemblyDirectoryPath = "C:\\bin";
const string assemblyFileNameSuffix = "_active";
var assemblyCurrentFileName = "test01_active.dll";
var assemblyOriginalFileName = "test01.dll";
var originalFilePath = Path.Combine(assemblyDirectoryPath, assemblyOriginalFileName);
var currentFilePath = Path.Combine(assemblyDirectoryPath, assemblyCurrentFileName);
if(File.Exists(originalFilePath))
{
File.Delete(currentFilePath);
File.Move(originalFilePath, currentFilePath);
}
Assembly asm = Assembly.LoadFrom(currentFilePath);
Type type = asm.GetType("Test.Program");
MethodInfo methodInfo = type.GetMethod("Run");
object[] parametersArray = new object[] { };
var result = methodInfo.Invoke(methodInfo, parametersArray);

Facing error during catalog refresh, the new dll is not used

I am trying to create a POC with mef where i have the requirement to load dll dynamically in an all ready running project , for this i have created one console application project and a class Library
project .
the code for console application project is as follows-
namespace MefProjectExtension
{
class Program
{
DirectoryCatalog catalog = new DirectoryCatalog(#"D:\MefDll", "*.dll");
[Import("Method1", AllowDefault = true, AllowRecomposition = true)]
public Func<string> method1;
static void Main(string[] args)
{
AppDomainSetup asp = new AppDomainSetup();
asp.ShadowCopyFiles = "true";
AppDomain sp = AppDomain.CreateDomain("sp",null,asp);
string exeassembly = Assembly.GetEntryAssembly().ToString();
BaseClass p = (BaseClass)sp.CreateInstanceAndUnwrap(exeassembly, "MefProjectExtension.BaseClass");
p.run();
}
}
public class BaseClass : MarshalByRefObject
{
[Import("Method1",AllowDefault=true,AllowRecomposition=true)]
public Func<string> method1;
DirectoryCatalog catalog = new DirectoryCatalog(#"D:\MefDll", "*.dll");
public void run()
{
FileSystemWatcher sw = new FileSystemWatcher(#"D:\MefDll", "*.dll");
sw.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.Size;
sw.Changed += onchanged;
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
Console.WriteLine(this.method1());
sw.EnableRaisingEvents = true;
Console.Read();
}
void onchanged(object sender, FileSystemEventArgs e)
{
catalog.Refresh();
Console.WriteLine(this.method1());
}
}
}
the library project which satisfy import looks as follow-
namespace MefLibrary
{
public interface IMethods
{
string Method1();
}
public class CallMethods : IMethods
{
[Export("Method1")]
public string Method1()
{
return "Third6Hello";
}
}
}
once i compile the library project(MefLibrary) and put the dll in D:\MefDll location and run the console application for first time i will see the output as
Third6hello on screen
but now if i change the implementation of method1 and make it return "third7hello" build MEF Library project and replace at D:\MefDll while my console app is running the onchanged handler even after calling catalog refresh prints
Third6hello on screen rather than third7hello
Whether anyone knows what is the reason for this , if yes please help.
DirectoryCatalog.Refresh will only add new or remove existing assemblies. It will not update an assembly. A crude workaround is:
Move the updated assembly to a temp folder.
Call DirectoryCatalog.Refresh. This will remove the part(s) contained in the assembly.
Move the assembly back to the watched folder
Call DirectoryCatalog.Refresh. This will add the updated part(s) contained in the assembly.
Note:
For this to work your "plugin" assemblies have to be strong named and with different version numbers (AssemblyVersionAttribute). This is needed because when parts are removed using the DirectoryCatalog.Refresh the actual assembly will not be unloaded. Assemblies can only be unloaded when the whole application domain is unloaded. So if DirectoryCatalog.Refresh finds a new assembly it will create an AssemblyCatalog using the assembly filepath. AssemblyCatalog will then call Assembly.Load to load the assembly. But this method will not load an assembly that has the same AssemblyName.FullName with an already loaded assembly.
Make sure that the steps I mention will not trigger another FileSystemWatcher.Changed event. For example you could use this approach.
Your program will need to have write access on the watched folder. This can be a problem if you deploy in the %ProgramFiles% folder.
If you need thread-safety you can consider creating your CompositionContainer with the CompositionOption.IsThreadSafe flag.
As I mentioned this is a workaround. Another approach would be to download MEF's source code and use DirectoryCatalog.cs as a guideline for your own directory catalog implementation.
Once a dll is loaded in an app domain it can't be unloaded from that domain. Only the whole domain can be unloaded. As such it is not easy to implement what you are after. It is possible to constantly scan for the changes and load new copies and repoint the calls (you will be accumulating more and more useless assemblies in your domain this way), but I don't believe this is something that MEF implements out of the box. In other words the behaviour you are observing is by design.
The implementation of this can be also tricky and bug prone because of state. Imagine you set a filed in a class instance of the old DLL and assign it to a variable. Then the new dll comes through. What happens to the old instance and its fields? Apparently they will stay the same and now you have different version of your plug-in in use in memory. What a mess!
And in case you are interested here is the reason why there isn't an Assembly.Unload method. And possible (conceptual) workaround.

How to read names and versions of assemblies

I have to create an application to read the name of all DLLs (assemblies) in my application path along with its version. And also to read the same of all the dll in the sub folders.
How to do this in C#. Can any one help me with sample code?
EDIT : how to read details of Legacy dlls( External dlls- not created in .NET)
Thanks in advance.
You should search through your given root directory by calling Directory.GetFiles(). You can run through the result and load every assembly by calling Assembly.ReflectionOnlyLoadFrom() (cause if you load it that way it won't be added to the AppDomain, thous no unload is needed).
With these Assembly classes you can access the GetName() function and take a look into the Version property to get the version information.
Other properties, that are not easily to get, can be reached through the GetCustomAttribute() function like this:
((AssemblyCopyrightAttribute)assembly.GetCustomAttribute(typeof(AssemblyCopyrightAttribute), true).Copyright
With these informations you should be able to built up the list you like.
Update:
And here's the obligatory linq code sample:
var rootPath = #"C:\MyRoot\Folder";
var query = Directory.GetFiles(rootPath, "*.dll", SearchOption.AllDirectories)
.Select(fileName =>
{
try
{
return Assembly.ReflectionOnlyLoadFrom(fileName);
}
catch
{
return null;
}
})
.Where(assembly => assembly != null)
.Select(assembly => new
{
Version = assembly.GetName().Version.ToString(),
Name = assembly.GetName().Name
});
foreach (var infos in query)
{
Console.WriteLine(infos.Name + " " + infos.Version);
}
Update 2:
So to get it from normal DLLs you should take a look into this question.
I'm a bit confused on what you actually want, but check the Assembly class: http://msdn.microsoft.com/en-us/library/system.reflection.assembly.aspx
Use Assembly.Load. There is one problem, you can't unload it, so if you do it too much times your memory will be filled by garbage.
If you call AssemblyName.GetAssemblyName, the assembly doesn't get loaded into your appdomain.
To include subfolders, you'll likely need to write a recursive function. Directory.GetDirectories or DirectoryInfo.GetDirectories can be used to retrieve all subfolders.
Use Assembly.GetName() to get an object, where you can extract the assembly version, as one part of an assemblies name is its version.
As already mentioned above, an assembly loaded with Assembly.Load cannot be unloaded and therefore remains in memory. You can however create a separate AppDomain, which you can unload again. Data transfer between AppDomains is possible by passing serializable objects, which is no problem for you because you just want to pass a string.
If you actually want to load the assemblies in the context of an extendable application, have a look at MEF.
Best Regards,
Oliver Hanappi
Use Assembly.ReflectionOnlyLoadFrom if you don't need to execute any code from that assembly and you want only gather some info about members contained inside.

Assembly.GetTypes() - ReflectionTypeLoadException

We implement a plugin framework for our application and load plugin assemblies using Assembly.Loadfrom. We then use GetTypes() and further examine the types with each plugin file for supported Interfaces.
A path for the plugins is provided by the user and we cycle through each of the files in the folder to see if it (the plugin) supports our plugin interface. If it does, we create an instance, if not we move onto the next file.
We build two versions of software from the one code base (appA_1 and appA_2).
Loading the plugins works well when the plugins are loaded by the application that was built at the same time as the plugin file. However if we build appA_2 and point to the plugin folder of appA_1, we get an exception when GetTypes() is called.
A basic version of our code is;
var pluginAssembly = Assembly.LoadFrom(FileName);
foreach (var pluginType in pluginAssembly.GetTypes())
{
We get a "ReflectionTypeLoadException" exception.
This is concerning because we want our application to be able to load the types of any plugin, built by anyone. Is there something we are missing?
EDIT:
After iterating through the LoaderExceptions we have discovered that there is a single file libPublic.dll that generates a System.IO.FileNotFoundException exception. The strange thing is that this file resides in the application directory and the plugin is referenced to the project file.
EDIT 2:
In the exception log we find the following
"Comparing the assembly name resulted in the mismatch: Revision Number"
A few things:
Make sure you don't have duplicate assemblies in the plugin directory (i.e. assemblies that you're already loading in your main app from your app directory.) Otherwise, when you load your plugin, it may load an additional copy of the same assembly. This can lead to fun exceptions like:
Object (of type 'MyObject') is not of type 'MyObject'.
If you're getting the exception when instantiating a type, you may need to handle AppDomain.AssemblyResolve:
private void App_Startup(object sender, StartupEventArgs e)
{
// Since we'll be dynamically loading assemblies at runtime,
// we need to add an appropriate resolution path
// Otherwise weird things like failing to instantiate TypeConverters will happen
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var domain = (AppDomain) sender;
foreach (var assembly in domain.GetAssemblies())
{
if (assembly.FullName == args.Name)
{
return assembly;
}
}
return null;
}
I realize it's a bit strange to have to tell the CLR that, in order to resolve an assembly, find the assembly with the name we're using to resolve, but I've seen odd things happen without it. For example, I could instantiate types from a plugin assembly, but if I tried to use TypeDescriptor.GetConverter, it wouldn't find the TypeConverter for the class, even though it could see the Converter attribute on the class.
Looking at your edits, this is probably not what's causing your current exception, though you may run into these issues later as you work with your plugins.
Thanks to this post I could solve the ReflectionTypeLoadException that I was getting in a UITypeEditor. It's a designer assembly (a winforms smart-tag used at design-time) of a custom class library, that scan for some types.
/// <summary>
/// Get the types defined in the RootComponent.
/// </summary>
private List<Type> getAssemblyTypes(IServiceProvider provider)
{
var types = new List<Type>();
try
{
IDesignerHost host = (IDesignerHost)provider.GetService(typeof(IDesignerHost));
ITypeResolutionService resolution = (ITypeResolutionService)provider.GetService(typeof(ITypeResolutionService));
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
foreach (var assembly in ((AppDomain)sender).GetAssemblies())
{
if (assembly.FullName == args.Name)
{
return assembly;
}
}
return null;
};
Type rootComponentType = resolution.GetType(host.RootComponentClassName, false);
types = rootComponentType.Assembly.GetTypes().ToList();
}
catch
{
}
return types;
}
You are getting an assembly version mismatch. Since your plugins refer to this libPublic.dll, you must version it carefully and in particular not bump its revision/build/etc. numbers at every compile.

How to embed a satellite assembly into the EXE file

I got the problem that I need to distribute a C# project as a single EXE file which is not an installer but the real program. It also needs to include a translation which currently resides in a subdirectory.
Is it possible to embed it directly into the binary?
The short answer is yes, there is a program called Assembly Linker (AL.exe) that will embed assemblies in this way. Its main use case is localization, sounds like that is what you need it for too. If so, it should be straightforward.
Eg:
al /t:lib /embed:strings.de.resources /culture:de /out:MyApp.resources.dll
or
al.exe /culture:en-US /out:bin\Debug\en-US\HelloWorld.resources.dll /embed:Resources\MyResources.en-US.resources,HelloWorld.Resources.MyResources.en-US.resources /template:bin\Debug\HelloWorld.exe
This is an example walkthrough of it MSDN with the above examples and more. Also you may want to read this blog post which explains its usage a bit further.
Here it is the simplest solution which I saw in the Internet:
How to embed your application’s dependent DLLs inside your EXE file
also handy implementation of this solution:
http://code.google.com/p/costura/wiki/HowItWorksEmbedTask
Another option is to embed the other assemblies as an EmbededResource. Then handle the app domains AssemblyResolve, from here you can read the assembly from the resource and load it into the runtime. Something like the following:
public class HookResolver
{
Dictionary<string, Assembly> _loaded;
public HookResolver()
{
_loaded = new Dictionary<string, Assembly>(StringComparer.OrdinalIgnoreCase);
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string name = args.Name.Split(',')[0];
Assembly asm;
lock (_loaded)
{
if (!_loaded.TryGetValue(name, out asm))
{
using (Stream io = this.GetType().Assembly.GetManifestResourceStream(name))
{
byte[] bytes = new BinaryReader(io).ReadBytes((int)io.Length);
asm = Assembly.Load(bytes);
_loaded.Add(name, asm);
}
}
}
return asm;
}
}
ILMerge will create a single exe file for your application. You can download it from Microsoft. It merges assemblies together and can internalize them so that the merged classes are set to internal. This is what I have used to create single file releases a number of times. It is pretty easy to integrate into your build process.

Categories

Resources