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


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

  • Comments 69

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)

  • How does this compare to ILMerge?  Any advantages to one over the other?

  • Jeffrey Richter, you rock!  I still have Applied .NET Framework Programming on my bookshelf at work and CLR via C# 2 on my bookshelf at home.  Can't wait for CLR via C# 3 to come out.  

  • David, here's a response from Jeff:

    ILMerge produces a new assembly file from a set of existing assemblies. This means that the original assemblies lose their identity (name, version, culture, and public key).

    What I am showing here is creating an assembly that embeds the EXISTING assemblies into it so that they do not lose their identity at all.

    Because the original assemblies keep their identity, more scenarios work correctly, such as serialization & security.

    ILMerge is most useful when all the assemblies being merged are produced by a single organization/company.

    What I propose can be used with assemblies produced by different organzations/companies.

  • I've just implemented this on my current project and it's saved loads of installation and build hassles.

    Cheers!!

  • What if I don't have an EXE and instead I am building a library?

    How do I achive the same results for a DLL.

    I'm wondering what would be an entry point for a DLL where I could attach do AssemblyResolve event.

  • tc, here's a response from Jeff:

    This is a great question and I wish there was a great solution for this problem but I am unable to come up with one.

    I tried writing a DllMain method in native C code and then adding a C# .netmodule to this assembly. The idea being that Windows will invoke the DllMain method and this method would invoke some managed code that would register with the AssemblyResolve event. But, this didn’t work because a DllMain method cannot call managed code due to potential deadlocking due to the Windows loader-lock. I even thought about having DllMain spawn a worker thread to do this to avoid the loader-lock issue. But, then there is a race condition which occurs and your program may work sometimes but not consistently.

    Then, I tried an EXE that registers with the AssemblyResovle event and the callback code would look at the ResolveEventArgs’s RequestingAssembly property and try to find the DLL in the requesting assembly. But this doesn’t work for two reasons.  First, the DLL might not be able to rely on the EXE (or host app) registering interest in this event. Second, and more importantly, when the RequestingAssembly is in the Load context (as is the most-common case), then the RequestingAssembly property returns null and so there is no way to make this work anyway.

    Then, I tried scanning having the event scan through all the currently-loaded assemblies’ manifest resources looking for an embedded assembly with the name of the assembly trying to be loaded. You can get this to work but it can potentially be a big runtime performance hit and it still suffers the same problem as before: it requires the EXE (or host) register with the AssemblyResolve event and do the right thing.

    The best option I can think of is to have the EXE (or host) explicitly call some initialization method in the not-embedded library so this method can register its interest in the AssemblyResolve event.

    Another option, is to have various places in the assembly’s code register with the event using some kind of singleton pattern. That is, if the DLL hasn’t registered with the event do it else don’t do it. And sprinkle calls to do this work through various places in the library’s code. This is the best solution from a usability standpoint but it is error prone.

    I wish I had a better solution to offer you but I do not. It would be nice if .NET gave you a way to create a method that invoked when your DLL loads but due to the Windows’ load-lock issue it is just too easy for developers to get into a deadlock scenarios with the app so I think the CLR team did the right thing by not allowing this. So far, this scenario is the only one I know of that really suffers due to this limitation.

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

  • As the author of ILMerge, I think this is fantastic! If I had known about this, I never would have written ILMerge.

    Many people have run into problems using ILMerge for WPF applications because WPF encodes assembly identities in binary resources that ILMerge is unable to modify. But this should work great for them.

    Thanks!

  • As the author of ILMerge, I think this is fantastic! If I had known about this, I never would have written ILMerge.

    Many people have run into problems using ILMerge for WPF applications because WPF encodes assembly identities in binary resources that ILMerge is unable to modify. But this should work great for them.

    Thanks!

  • thanks for the tip, it works ONLY for a Full Trust Application, also for WPF (where ILMerge fails to merge libraries)

    what about the following scenario:

    I want to build WPF for Browser Application (with Partial Trust, because i don't want not to play around with installing certificates on client machines - this app should widely accessible).

    Furthermore, i want to obfuscate my exe (including these libraries from resource), and i am not sure, that obfuscation software can obfuscate resources (at least Eazfuscator, which i use is not doing this).

  • I second that, especially the issue with obfuscator.

    Any hints?

  • I think for obfuscator, you can simply embemed the obsfucated dlls....

  • I am trying to use the above in my application I threw AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve) is that correct?

  • I tried and also ran into a problem with this pop-up message:

    TestPortfolioDragDrop [name of .exe] has stopped working.

    I put the code in the constructor of MainWindow of a WPF Appliation called TestPortfolioDragDrop.

  • Hi,

    How/Where can I register the eventhandler early enought?

    If I register it in my Main()-Function it's too late because the CLR already checked the DLL-Reference before it calls my Main-Function and so it fails to load.

    I'm using C#/.Net2.x (vs2005)

    yours

    hr

  • OK, I found the solution:

    The function calling the DLL must reside in another class, then it will be loaded later. Now everything works.

    yours

    hr

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