In last month’s column, I presented an overview of .NET, looking at its structure and contents. This month, I want to look in more detail at .NET assemblies: what they are, why they matter and what you can do with them. I’ll be illustrating these concepts using some simple C# code and the tools supplied with both the .NET Framework and the .NET Framework SDK. First, though, an update on the current release status of the .NET Framework.
Between the time I submitted my first column and this one, Microsoft has released the latest version of the .NET Framework, .NET 2, along with Visual Studio 2005 and SQL Server 2005. Last month, I gave you some pointers to the relevant downloads, some of which may have now changed, so I’ve set up a new page at www.reskit.net, which contains all the URLs I’ll be referring to in each article. These URLs are updated whenever necessary, so they should be up to date. I’ll also be including additional pointers to more information, code snippets and anything else I can think of that you might find useful.
Before diving into how assemblies work, I think it’s useful to talk a little about how Windows loads programs and how .NET fits into the scheme of things. The Windows loader is an OS component that’s responsible for loading an executable program into memory and starting it running – a vital component that we tend to take for granted. In the Win32 environment, files that are executable – that is, EXE and DLL files – have a common internal format known as Portable Execution, or PE, format.
The ‘portable’ bit refers to the fact that at one time executable files could run on multiple platforms, so a common file format was needed. The PE format initially came from the VAX world, but was adopted for Windows NT. The basic PE format was first devised in the days of MS-DOS, over 30 years ago, but has been modified over time, and for .NET and 64-bit Windows it’s undergone further modifications. (While most things have moved on, it’s interesting that the first two bytes of any PE file still contain the characters ‘MZ’ after Mark Zbikowski, one of the original DOS architects.)
PE files start off with a small section of MS-DOS executable code, which was useful in the early days of Windows: if you tried to run a Windows executable on a machine without Windows loaded, the program could display a message pointing out that Windows was required. For more information about the PE format, see msdn.microsoft.com and msdn.microsoft.com
With that bit of background in place, what exactly is an assembly and why does it play a key role in the .NET architecture? An assembly is a unit of program code that you build and deploy as a single unit – much like a traditional EXE or DLL file. The assembly is the key unit of deployment under .NET, and an assembly is also a security boundary, in that you can grant permissions at the level of the single assembly (I’ll be covering .NET security more fully in a later article or articles). Figure 1 shows the contents of a typical assembly, which includes:
Assembly metadata: information that describes the assembly in detail.
Type metadata: information about the types this assembly implements.
Code: this is MS Interpreted Language (MSIL) code that implements the types defined in this assembly. MSIL code is compiled to native code by the Just-in-Time compiler whenever the application is run. As I’ll be covering this in a future article, you can also use the ngen.exe utility to compile an assembly into native code that resides in the Global Assembly Cache (GAC).