What Are Assemblies?


Before the .NET platform was introduced in the year 2002, you had to deal with the predecessors of assemblies: normal DLLs exporting global functions and COM DLLs exporting COM classes. Microsoft itself introduced the phrase “DLL Hell” to describe traditional problems with DLLs - problems known all too well.

Often applications break because a newly installed application overwrites a DLL that has also been used by another application. Sometimes the installation replaces a new DLL with an old one, because the installation program does not correctly check the versions or the versions are not correctly set. More often, an old DLL is replaced by a new version. Normally, this shouldn’t be a problem - the new DLL should be backward compatible with the old version; however, often that is not the case.

Windows 2000 introduced the side-by-side feature, which allows the installation of native DLLs in the application’s directory. With side-by-side, you can install a different version of an already installed, shared DLL to the directory of the application. The LoadLibrary() Windows API call was rewritten so that it first checks for a .local file in the application directory. If it is found, the API first checks if a DLL was in the same directory of the application, before the other mechanisms are used to find a shared DLL. This also modifies the fixed path that is in the registry for COM DLLs. Side-by-side is an afterthought and doesn’t solve all the issues, and even introduces some new problems with COM DLLs. Another feature of Windows 2000 or later Windows operating systems that deals with DLL Hell is file protection: system DLLs are protected from being overwritten by unauthorized parties. All of these features deal with the symptoms, not with the causes themselves.The versioning problems of DLLs exist because it’s not clear which version of a specific DLL is needed by each application. Dependencies are not tracked or enforced with the traditional DLL architecture. COM DLLs seem to solve a lot of the DLL problems because of a better separation of the implementation and the interface. The interface is a contract between the client and the component, which, according to COM rules, should never be changed and thus cannot break. However, even with COM, changes of implementations can break existing applications.

Side-by-side also supports COM DLLs. If you have ever tried side-by-side with COM DLLs, you have seen that it’s just a hack. New problems arise when using side-by-side COM DLLs. If you’re installing the same DLL over the old one (without uninstalling the old DLL), what happens when two versions of the same component use different threading configurations? The configuration information is taken from the last installed version. This problem exists because the configuration of a COM component is not stored in the component DLL itself but in the registry.

The Answer to DLL Hell

The .NET platform’s answer to DLL Hell and to all of its problems is assemblies. Assemblies are self-describing installation units, consisting of one or more files. One assembly could be a single DLL or EXE that includes metadata, or it can be made of different files, for example, resource files, metadata, DLLs, and an EXE. Installation of an assembly can be as simple as copying all of its files. An xcopy installation can be done. Another big feature of assemblies is that they can be private or shared. With COM this differentiation doesn’t exist, because practically all COM components are shared. If you search for a COM component in the registry or use OLEView, you have to walk through hundreds and hundreds of components. Only a small number of these components were ever meant to be used from more than one application; however, every component must have a global unique identifier (GUID).

There’s a big difference between private and shared assemblies. Many developers will be happy with just private assemblies. No special management, registration, versioning, and so on is needed with private assemblies. The only application that could have version problems with private assemblies is your own application. The private components you use within your application are installed at the same time as the application itself. Local application directories are used for the assemblies of the components, so you shouldn’t have any versioning problems. No other application will ever overwrite your private assemblies. Of course, it is still a good idea to use version numbers for private assemblies too. This helps a lot with code changes, but it’s not a requirement of .NET.

With private assemblies you can still have versioning problems during development time. For example, if a component you use in your application references version 1 of assembly X, and you use version 2 of assembly X in your application, which version of the assembly is copied to your application directory?

The answer depends on what assembly you have referenced first - this versioning problem must be solved during development time. On the installed system, a hot fix can be easily applied to an application by simply replacing a private assembly with a new version. The only application that could have problems with the new version is the one where this fix is applied, because no other applications can be influenced.

When using shared assemblies, several applications can use this assembly and have a dependency on it. With shared assemblies, many rules must be fulfilled - a shared assembly must have a special version number, a unique name, and usually it’s installed in the global assembly cache.

Features of Assemblies

The features of assemblies can be summarized as follows:

  • Assemblies are self-describing. It’s no longer necessary to pay attention to registry keys for apartments, to get the type library from some other place, and so on. Assemblies include metadata that describes the assembly. The metadata includes the types exported from the assembly and a manifest; the next section describes the function of a manifest.

  • Version dependencies are recorded inside an assembly manifest. Storing the version of any referenced assemblies in the manifest of the assembly lets you able to know exactly the version number of the referenced assembly that was used during development. The version of the referenced assembly that will be used can be configured by the developer and the system administrator. Later in this chapter, you learn which version policies are available and how they work.

  • Assemblies can be loaded side by side. With Windows 2000 you already have a side-by-side feature where different versions of the same DLL can be used on a system. .NET extends this functionality of Windows 2000, allowing different versions of the same assembly to be used inside a single process! How is this useful? If assembly A references version 1 of the shared assembly Shared, and assembly B uses version 2 of the shared assembly Shared, and you are using both assembly A and B, you need both versions of the shared assembly Shared in your application - and with .NET both versions are loaded and used.

  • Application isolation is ensured using application domains. With application domains a number of applications can run independently inside a single process. Faults in one application cannot directly affect other applications inside the same process.

  • Installation can be as easy as copying the files that belong to an assembly. An xcopy can be enough. This feature is named no-touch deployment. However, there are cases in which no-touch deployment cannot be applied, and a normal Windows installation is required. Deployment of applications is discussed in Chapter 15, “Deployment.”

Application Domains and Assemblies

Before .NET, processes were used as isolation boundaries, with every process having its private virtual memory; an application running in one process could not write to the memory of another application and thereby crash the other application. The process was used as an isolation and security boundary between applications. With the .NET architecture you have a new boundary for applications: application domains. With managed IL code, the runtime can ensure that access to the memory of another application inside a single process can’t happen. Multiple applications can run in a single process within multiple application domains (see Figure 16-1).

image from book
Figure 16-1

An assembly is loaded into an application domain. In Figure 16-1 you can see process 4711 with two application domains. In application domain A, the objects one and two are instantiated, one in assembly One, and two in assembly Two. The second application domain in process 4711 has an instance one. To minimize memory consumption, the code of assemblies is only loaded once into an application domain. Instance and static members are not shared between application domains. It’s not possible to directly access objects within another application domain; a proxy is needed instead. So in Figure 16-1, the object one in application domain B cannot directly access the objects one or two in application domain A without a proxy.

Tip 

Chapter 37, “.NET Remoting” offers more about proxies and communication across application domains.

The AppDomain class is used to create and terminate application domains, load and unload assemblies and types, and enumerate assemblies and threads in a domain. In this section, you program a small example to see application domains in action.

First, create a C# console application called AssemblyA. In the Main() method add a Console.WriteLine() so that you can see when this method is called. In addition, add the class Demo with a constructor with two int values as arguments, which will be used to create instances with the AppDomain class. The AssemblyA.exe assembly will be loaded from the second application that will be created:

 using System; namespace Wrox.ProCSharp.Assemblies.AppDomains {    public class Demo    {       public Demo(int val1, int val2)       {          Console.WriteLine("Constructor with the values {0}, {1}" +                " in domain {2} called", val1, val2,                AppDomain.CurrentDomain.FriendlyName);       }    }    class Program    {       static void Main()       {          Console.WriteLine("Main in domain {0} called",                AppDomain.CurrentDomain.FriendlyName);       }    } }

Running the application produces this output:

 Main in domain AssemblyA.exe called. Press any key to continue ...

The second project you create is again a C# console application: DomainTest. First, display the name of the current domain using the property FriendlyName of the AppDomain class. With the CreateDomain() method, a new application domain with the friendly name New AppDomain is created. Then load the assembly AssemblyA into the new domain and call the Main() method by calling ExecuteAssembly():

 using System; namespace Wrox.ProCSharp.Assemblies.AppDomains {    class Program    {       static void Main()       {          AppDomain currentDomain = AppDomain.CurrentDomain;          Console.WriteLine(currentDomain.FriendlyName);          AppDomain secondDomain =                AppDomain.CreateDomain("New AppDomain");          secondDomain.ExecuteAssembly("AssemblyA.exe");       }    } }

Before starting the program DomainTest.exe, reference the assembly AssemblyA.exe with the DomainTest project. Referencing the assembly with Visual Studio 2005 copies the assembly to the project directory, so that the assembly can be found. If the assembly cannot be found, a System.IO.FileNotFoundException exception is thrown.

When DomainTest.exe is run, you get the following console output. DomainTest.exe is the friendly name of the first application domain. The second line is the output of the newly loaded assembly in the New AppDomain. With a process viewer, you will not see the process AssemblyA.exe executing because there’s no new process created. AssemblyA is loaded into the process DomainTest.exe.

 DomainTest.exe Main in domain New AppDomain called Press any key to continue . . .

Instead of calling the Main() method in the newly loaded assembly, you can also create a new instance. In the following example, replace the ExecuteAssembly() method with a CreateInstance(). The first argument is the name of the assembly, AssemblyA. The second argument defines the type that should be instantiated: Wrox.ProCSharp.Assemblies.AppDomains.Demo. The third argument, true, means that case is ignored. System.Reflection.BindingFlags.CreateInstance is a binding flag enumeration value to specify that the constructor should be called:

 AppDomain secondDomain =    AppDomain.CreateDomain("New AppDomain"); // secondDomain.ExecuteAssembly("AssemblyA.exe"); secondDomain.CreateInstance("AssemblyA",    "Wrox.ProCSharp.Assemblies.AppDomains.Demo", true,    System.Reflection.BindingFlags.CreateInstance,    null, new object[] {7, 3}, null, null, null); 

The results of a successful run of the application:

 DomainTest.exe Constructor with the values 7, 3 in domain New AppDomain called Press any key to continue .  . .

Now you have seen how to create and call application domains. In runtime hosts, application domains are created automatically. ASP.NET creates an application domain for each Web application that runs on a Web server. Internet Explorer creates application domains in which managed controls will run. For applications, it can be useful to create application domains if you want to unload an assembly. You can unload assemblies only by terminating an application domain.

Important 

Application domains are an extremely useful construct if assemblies are loaded dynamically, and the requirement exists to unload assemblies after use. Within the primary application domain it is not possible to get rid of loaded assemblies. However, it is possible to end application domains where all assemblies loaded just within the application domain are cleaned from the memory.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net