Jeffrey Richter: Excerpt #2 from CLR via C#, Third Edition


Jeffrey Richter: Excerpt #2 from CLR via C#, Third Edition

  • Comments 67

Good morning, Jeffrey Richter here. My new book, CLR via C#, Third Edition (Microsoft Press, 2010; ISBN: 9780735627048), should be available via online retailers by February 15.  (Microsoft Press will alert you here when it is.)

Today I thought I’d share a section a section from Chapter 23, “Assembly Loading and Reflection,” with you. This section discusses how to embed your application’s dependent DLLs inside your EXE file, simplifying deployment by allowing you to distribute just one physical file.

> > > > >

Many applications consist of an EXE file that depends on many DLL files. When deploying this application, all the files must be deployed. However, there is a technique that you can use to deploy just a single EXE file. First, identify all the DLL files that your EXE file depends on that do not ship as part of the Microsoft .NET Framework itself. Then add these DLLs to your Visual Studio project. For each DLL file you add, display its properties and change its “Build Action” to “Embedded Resource.” This causes the C# compiler to embed the DLL file(s) into your EXE file, and you can deploy this one EXE file.

At runtime, the CLR won’t be able to find the dependent DLL assemblies, which is a problem. To fix this, when your application initializes, register a callback method with the AppDomain’s ResolveAssembly event. The code should look something like this:

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {

   String resourceName = "AssemblyLoadingAndReflection." +

      new AssemblyName(args.Name).Name + ".dll";

   using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) {

      Byte[] assemblyData = new Byte[stream.Length];

      stream.Read(assemblyData, 0, assemblyData.Length);

      return Assembly.Load(assemblyData);

   }

};

Now, the first time a thread calls a method that references a type in a dependent DLL file, the AssemblyResolve event will be raised and the callback code shown above will find the embedded DLL resource desired and load it by calling an overload of Assembly’s Load method that takes a Byte[] as an argument.

 

Jeffrey Richter (http://Wintellect.com)

  • Simon Cropp, Jeff has the following response for you:

    I have used Einar Egilsson's suggestion myself with great success. I have not tried the MSBuild task myself, but this seems like a great way to go.

    --Jeffrey Richter (http://Wintellect.com)

  • Hi Jeff,

    I am not sure if I understand how exactly to implement this? In the sense that I already have other methods dependent on these assemblies. Kindly help as I am new to this.

    Thanks

    John

  • John Morgan,

    Jeffrey writes this in response:

    I have an EXE that depends on DLL1 and DLL1 depends on DLL2. DLL2 requires no special code.

    In DL1, I add the following code plus whatever I would normally put in DLL1:

      internal static class ModuleInitializer {

         // See tech.einaregilsson.com/.../module-initializers-in-csharp

         // and code.google.com/.../HowItWorksEmbedTask

         internal static void Run() {

            AppDomain.CurrentDomain.AssemblyResolve += ResolveEventHandler;

         }

         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);

            }

         }

      }

    For the DLL1 project, I have the following post-build event (for InjectModuleInitializer.exe, see the comments above):

         $(ProjectDir)InjectModuleInitializer.exe $(TargetPath)

    Then in my EXE, I have the same ResolveEventHandler method and in Main, I have this:

         AppDomain.CurrentDomain.AssemblyResolve += ResolveEventHandler;

    --Jeffrey Richter (http://Wintellect.com)

  • Hi Jeff,

    Thanks for the prompt reply. What is the function of DLL2? Is it where the external assemblies have been used?

    Regards

    John

  • John Morgan, Jeffrey says:

    DLL2 is just dome library that DLL1 depends on; your app may not have a DLL2.

    The solution I describe allows you to package DLL1 and what it depends on (DLL2) together into the EXE and have everything work even if the EXE doesn’t depend on DLL2 directly.

    --Jeffrey Richter (http://Wintellect.com)

  • Hi Jeffrey,

    I would like to impliment your approch of loading and resolving DLL from application resource, but I am a starter, I don't know C#. Is it possible to port this code to VB (.Net 4.0), if possible please post the code, that would be helpfull for many programmers like me.

    Thanks in advance,

    Nirmal Rajmohan

  • I just did this in Vb.net. My Application looks like this:

    Class Application

       Private Sub Application_Startup(

                 sender As Object,

                 e As System.Windows.StartupEventArgs) _

                     Handles Me.Startup

           AddHandler AppDomain.CurrentDomain.AssemblyResolve,                                                                                              

                  AddressOf pHandleAssemblyResolver

       End Sub

       Private Function pHandleAssemblyResolver(

               sender As Object,

               e As System.ResolveEventArgs) As Reflection.Assembly

           If e.Name.Contains("HtmlAgilityPack") Then

               Return Reflection.

                      Assembly.

                      Load(My.Resources.HtmlAgilityPack)

           Else

               Return Nothing

           End If

       End Function

    End Class

    I added "HtmlAgilityPack.dll" as a file resource. And then changed the property "Copy local" of the references to it to False.

  • Thank you so much David Sherwood.

    I will check this solution soon and will post the results.

    Can we load an ActiveX library (*.OCX) using this method? Is there any way to do that?

    Thanks and regards,

    Nirmal Rajmohan

  • If you put the AssemblyResolve event handler registration code in <Module>::.cctor (i.e., the module's .cctor method, the "global" .cctor), it should work even if it's a DLL file. This is what most obfuscators do.

  • The assembly load results should be cached, since you might have several resolve calls to the same dll. For example if a dll is referenced in <configuration><configSections> and the same dll is later referenced by code, you might have two AssemblyResolve calls. The args.Name parameter might be different, but the "new AssemblyName(args.Name).Name" will be equal, so use that as the cache key. A static Dictionary<string,Assembly> should suffice?

  • Jeffrey, I am afraid that this more and more widely pattern proposed here provokes a waste of (the so precious) process memory.

    If an asm A embeds and asm B as a resource, the B code will be duplicated 3 times in the process:

    1) in the A file image pre JIT

    2) such as the B assembly file image pre JIT

    3) such as the B assembly post JITed

    The 1) case introduce the waste.

    Unless I am wrong, it would be a good idea to tell this caveat to enthusiasts about this pattern, before their program memory burst.

  • Hi,

    I'm quite new to this but this solution is exactly what I need for deployment of my application.

    My problem is that I don't know where to put this in my application's code. When I paste the code into my main forms main method there are several errors.

    So please can you tell me where to put this code extract to make it work?

    yours

    Pete

  • I didn't notice Matty's question get answered and I have the same question.

    I added the code to load my dll as indicated in this post, but I'm still getting the following error and cannot compile:

    The type or namespace name 'ClassInDLL' could not be found (are you missing a using directive or an assembly reference?)

  • For me this is the most important info I have seen in a long time. Why the hell is this technique not an option in the development GUI of VB.NET?

  • I was struggling to implement this in a console app and I finally found this.

    sanganakauthority.blogspot.co.uk/.../creating-single-exe-that-depends-on.html

    adding the event handler as in the above and using the slightly cleaner method illustrated by Jeff here worked beautifully for me.

    Thanks Jeff

Page 3 of 5 (67 items) 12345
Leave a Comment
  • Please add 1 and 1 and type the answer here:
  • Post