Click here to Skip to main content
15,879,064 members
Articles / Programming Languages / C#
Article

Embed an Assembly as a Resource

Rate me:
Please Sign up or sign in to vote.
4.91/5 (10 votes)
2 Feb 2010CPOL4 min read 55.9K   1.1K   50   6
Embed an assembly as a resource, exemplified with Log4Net

Introduction

If your application uses another dependent assembly, you have to deploy this assembly also together with your application. In some cases however, you wish you could only deploy your application assembly – and nothing else. To achieve this, you could embed the dependent assembly as a resource into your application assembly.

Unfortunately, this is not the whole story. You also need some other things, which I will describe in the following. As an example, I will use the embedding of log4net.dll.

I assume you already have an application using log4net. If not, it is a good idea to first create the application with the complete functionality. Not before nearly the end, embed the dependent assembly.

Embed the Resource

The start is quite intuitive: add a folder Resources to your project, add the log4net.dll to this folder, and change its build action to Embedded Resource.

Load the Assembly

The next step is to load the assembly from the resources. This must be done before the first use of the assembly.

First, you must determine the name of the resource:

C#
Assembly assembly = Assembly.GetExecutingAssembly();
string assemblyName = assembly.FullName.Split(',')[0];
string resourceName = assemblyName + ".Resources.log4net.dll";

Now you can load the assembly:

C#
Stream imageStream = assembly.GetManifestResourceStream(resourceName);
long bytestreamMaxLength = imageStream.Length;
byte[] buffer = new byte[bytestreamMaxLength];
imageStream.Read(buffer, 0, (int)bytestreamMaxLength);
log4NetAssembly = Assembly.Load(buffer);

Remove the Reference

Until now, you have gained nothing. Because log4net.dll is still referenced in your project, the .NET loader tries to load it together with your assembly. Unfortunately, the .NET loader does not know that you embedded the log4net.dll in your assembly. Therefore, it produces an error during the load of your assembly:

Could not load file or assembly ' ... ' or one of its dependencies. 
The system cannot find the file specified.

This happens before the code in your assembly starts. Therefore, you have no chance to change this behavior. The only chance is to remove the reference from your assembly.

Create a Proxy

As soon as you remove the reference, you will not be able to compile your application anymore (which solves the deployment problem, albeit in an unwished way). This is quite clear, because now the compiler does not know what to do with your log4net calls.

To make your application compilable again, you have to add your own log4net classes, which call the original log4net assembly via reflection. Fortunately, you have to implement only these classes and methods you use.

A good idea is to create a new folder called Log4NetProxy. To this folder, you can add your own implementation for every needed class.

A typical use of log4net looks like this:

C#
ILog logger = LogManager.GetLogger(typeName);
logger.Info(message);

In your proxy, now you need implementations for:

  • ILog
  • LogManager
  • LogImpl (which is returned by LogManager.GetLogger() and implements ILog)

Additionally, you should use a utility class called Loader.

Loader

The loader class should have a static variable, which contains the loaded assembly (as described above). Additionally, it should contain a helper method to get a specific type from the assembly:

C#
static class Loader
{
  private static readonly Assembly log4NetAssembly = LoadAssembly();

  static static Assembly LoadAssembly()
  {
    ...
    return Assembly.Load(buffer);
  }

  internal static Type GetType(string typeName)
  {
    Type type = null;
    if (log4NetAssembly != null)
    {
      type = log4NetAssembly.GetType(typeName);
      if (null == type)
        throw new ArgumentException("Unable to locate type");
    }
    return type;
  }
}

ILog

The interface is quite simple. Just add all members from the original interface, which you use. For the example above, the following code is enough:

C#
interface ILog
{
  void Info(object message);
}

LogManager

From the LogManager’s methods, you also need only one method. It first gets the LogManager type from the loaded assembly, and then it invokes the method GetLogger via reflection. Finally, the returned object will be encapsulated in LogImpl. The last step is necessary, because otherwise the using client (your application) would have to use reflection itself. But you want to leave your code as less changed as possible.

Overall, you get the following code:

C#
static class LogManager
{
  public static ILog GetLogger(string name)
  {
    ILog logger = null;
    Type logManager = Loader.GetType("log4net.LogManager");
    if (logManager != null)
    {
      MethodInfo method = logManager.GetMethod("GetLogger", 
                            new[] { name.GetType() });
      logger = new LogImpl(method.Invoke(null, new[] { name }));
    }
    return logger;
  }
}

LogImpl

LogImpl also uses reflection to call the original implementation. Because it has instance methods, the real instance is a parameter to the constructor:

C#
class LogImpl : ILog
{
  private readonly object instance;

  internal LogImpl(object instance)
  {
    this.instance = instance;
  }

  public void Info(object message)
  {
    MethodInfo method = instance.GetType().GetMethod("Info", 
                          new[] { message.GetType() });
    method.Invoke(instance, new[] { message });
  }
}

Use the Proxy

The rest is quite easy: in every class, you have a compile error because of the missing log4net assembly, replace the statement:

C#
using log4net;

with:

C#
using Log4NetProxy;

Handle the AssemblyResolve Event

For the most scenarios, the so far outlined solution should work. However, sometimes assemblies are also referenced in another way. In the case of log4net, this is its configuration, which looks like:

XML
<log4net>
  <appender name="MyAppender" type="log4net.Appender.ConsoleAppender">
    ...
  </appender>
  ...
</log4net>

The XML above will be parsed from inside of log4net. However, it will try to load the type log4net.Appender.ConsoleAppender from the assembly log4net. Because the .NET loader does not know anything about our own loading, it looks itself for the log4net assembly. Again, we get:

Could not load file or assembly 'log4net' or one of its dependencies. 
The system cannot find the file specified.

To prevent this, you can give the loader a hint by hooking the AssemblyResolve event:

C#
AppDomain.CurrentDomain.AssemblyResolve +=
  delegate(object o, ResolveEventArgs args)
  {
    if (args.Name == "log4net")
      return log4NetAssembly;
    else
      return null;
  };

The best place for this piece of code is immediately after the successful loading of the assembly.

History

  • 31-Jan-2010
    • Initial public release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
SuggestionInteresting approach Pin
Kelvin3329-Oct-11 15:47
Kelvin3329-Oct-11 15:47 
QuestionCould be simpler? Pin
BigMetsFan22-Aug-10 16:31
BigMetsFan22-Aug-10 16:31 
First, to the previous commenters, ILMerge is definitely easier, but doesn't support assemblies with XAML, so will not support WPF.

Seems like the approach described here would work, but seems like there is extra work being done. Please see the excellent article at http://blog.mahop.net/post/Merge-WPF-Assemblies.aspx#comment. Basically, you just need to create a new ResolveEventHandler delegate, and load the assembly from the resource in there. You also would not need to remove the references from the project.
GeneralExcellent - and only the begining Pin
alex turner2-Feb-10 22:22
alex turner2-Feb-10 22:22 
GeneralExepack.NET Pin
Huisheng Chen2-Feb-10 22:22
Huisheng Chen2-Feb-10 22:22 
GeneralMore simple way with ILMERGE Pin
Olaf Herrmann2-Feb-10 21:50
professionalOlaf Herrmann2-Feb-10 21:50 
GeneralILMerge Pin
fmaeseele2-Feb-10 21:48
fmaeseele2-Feb-10 21:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.