Embedding, Streaming, and Loading Assemblies
OK, this might not be such a big deal for some people, but have you ever had a third party .NET dll that you wanted to distribute with your .exe, but you'd like to just distribute just one exe and not two seperate files (And you don't want to write an installer)? Well, you can actually do this by embedding the .dll file as a resource, and then loading it after listening for the AppDomain.CurrentDomain.AssemblyResolve event.
It's not rocket science, but I found it interesting.
/*========================================================================
File: Embed.cs
Summary: This file shows an example of how an assembly may be loaded from
an embedded resource instead of from disk.
Instructions for use:
1. Include the Embed.cs file in the project or assembly in which you wish to
'embed' an assembly.
2. As early in the startup code as possible ( preferably in
a call to Dennany.Embed.Init(). This will register Embed.LoadComponentAssembly()
as the callback method for the AssemblyResolve event.
3. Add the 'dynamic' assembly to your project as an Embedded Resource.
This may be done from the menu:
Project -> Add Existing Item (Browse to appropriate .dll file here)
Make certain that you set the properties of the .dll to "Embedded Resource"!
(This may be done from the Solution Explorer by right-clicking on the .dll file,
selecting properties, and setting the "Build Action" to "Embedded Resource.")
Special Note: Because of the way the CLR loads assemblies, you may not reference
a type in one of these embedded assemblies before the call to Embed.Init().
You may also not reference a type in one of these embedded assemblies in the same method
that the call to Embed.Init() is made.
Copyright (c) 2003, Jerry Dennany
========================================================================*/
using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Resources;
using System.IO;
using System.Diagnostics;
namespace Dennany {
public sealed class Embed {
// this class is static, and should not be instantiated.
private Embed() {}
// Init is the only public callable method of this class. It should be called once
// (and only once!)
// to register the LoadComponentAssembly method as the event handler to call should the
// AssemblyResolve event get fired.
public static void Init(){
ResolveEventHandler loadAssembly = new ResolveEventHandler(LoadComponentAssembly);
AppDomain.CurrentDomain.AssemblyResolve += loadAssembly;
}
// This method is called when an assembly fails to load via probing path, GAC, etc.
// Note that we return null to the CLR if we have no assembly to load. This is
// expected behavior, so that the CLR may notify the rest of the application that
// it could not load the assembly in question.
static Assembly LoadComponentAssembly(Object sender, ResolveEventArgs args) {
// We'll use this reference fairly often in the future...
Assembly assembly = Assembly.GetExecutingAssembly();
// Get the requested assembly's simple name (no namespace info or file extension)
string simpleName = args.Name.Substring(0, args.Name.IndexOf(',') );
string dllImageResourceName = getResourceLibName( simpleName, assembly );
return streamFromResource(dllImageResourceName, assembly);
}
private static string getResourceLibName(string simpleLibName){
return getResourceLibName( simpleLibName, Assembly.GetExecutingAssembly() );
}
// We will go through the list of resources in the assembly and using the
// simpleLibName, we will find if the dll resource is embedded in the assembly
// Note that we return null on purpose if we didn't find anything.
// This is because we also want to return null to the CLR if we have no assembly to load.
private static string getResourceLibName(string simpleLibName, Assembly assembly) {
if ( simpleLibName == null || assembly == null ) return null;
simpleLibName += ".dll"; // assume that the file ends in this extension.
string dllImageResourceName = null;
// We will iterate through the list of resources in this assembly,
// looking for the name of the assembly that failed to load from disk
foreach (string resourceName in assembly.GetManifestResourceNames()) {
if (resourceName.Length < simpleLibName.Length) continue;
// if the simpleName and resourceName end the same (we drop namespace info here),
// then this should be the embedded assembly that we are looking for.
if (String.Compare(simpleLibName,
0,
resourceName,
(resourceName.Length - simpleLibName.Length),
simpleLibName.Length,
true) == 0 ) {
dllImageResourceName = resourceName;
}
}
return dllImageResourceName;
}
private static Assembly streamFromResource(string dllImageResourceName){
return streamFromResource(dllImageResourceName, Assembly.GetExecutingAssembly() );
}
// this is the 'workhorse' of the class. Once we've got a resource name in the assembly,
// we stream the resource to a byte array, and load the Assembly from the byte array.
private static Assembly streamFromResource(string dllImageResourceName, Assembly assembly){
if ( dllImageResourceName == null || assembly == null ) return null;
Stream imageStream;
imageStream = assembly.GetManifestResourceStream(dllImageResourceName);
long bytestreamMaxLength = imageStream.Length;
byte[] buffer = new byte[bytestreamMaxLength];
imageStream.Read(buffer,0,(int)bytestreamMaxLength);
return AssemblyBuilder.Load(buffer);
}
}
}