C#.Net Static Linking Adventures

My app uses two DLLs - say Lib1 and Lib2 from a CommonDll folder.

With normal linking these DLL's also need to be placed with App executable. But previously we have been providing only App exe only.
Hence to keep the compatibility and to lessen deployment headaches, the above 2 DLLs will be statically linked in the executable.

In .Net world this not so straightforward as setting a static linking flag in linker.

Here we need to provide following steps. I have added the rationale for each step alongwith.

1. Add the DLL's to project as resources - preferably in a Resources folder. Go into these DLLs properties and change "Build Action" from "Content" to "Embedded Resource". Now the DLLs shall get embedded in the exe.

2. Provide a handler for AssemblyResolve event. This new handler looks for assemblies in Resources and if found, loads them there.
This handler is like below:

private static Assembly ResolveEventHandler(Object sender, ResolveEventArgs args)
{
    String dllName = new AssemblyName(args.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);
    }
}

To add this to your program add first line as:
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler( ResolveEventHandler);

3. Add a preBuild step where you will copy the latest DLLs from CommonDll folder to your Resources folder.
For this go to Project Properties > Build Events > Pre-Build Event Command Line

call "$(DevEnvDir)..\Tools\vsvars32.bat"
copy "$(SolutionDir)CommonDll\Lib1.dll" "$(ProjectDir)Resources"
copy "$(SolutionDir)CommonDll\Lib2.dll" "$(ProjectDir)Resources"

This ensures you have latest version of assemblies. Incidently we have been putting all solution dependent lib assemblies in CommonDll folder.

4. Above three steps are not sufficient because of the nature of loading of assemblies. CLR tries to load all dependent assemblies at start of function. So with normal main code with the handler override at start, won't work. Because CLR would be looking for dependent assemblies at start of Main and before executing our AssemblyResolve handler registration.

To handle this - in main() provide two functions:
void Main()
{
    Initialise();
    RealMain();
}

void Initialise()
{
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler( ResolveEventHandler);
}

and

Void RealMain()
{
    // Your actual Main code here
}

This way the assembly hander is registered before any assembly calls are made.

5. Now when you build the project you will find the Lib1 and Lib2 dll's in bin/Debug folder. But we don't want them here, dont we? Ok. To fix this - go to References - Right Click on Lib1 > click on Properties. Here change "Copy Local" to "False". Also make the same change for Lib2. Now delete the dll's from bin/Debug and rebuild the project. Now you will find only App exe there.

This way we are providing single App exe with all common code from Lib1 and Lib2 dlls.

Comments

Popular posts from this blog

Morning Quotes

QCalendarWidget CSS Stylesheeting

Selecting new phone plan