Assemblies
You deploy .NET types in Assemblies. A .NET assembly is the unit of deployment, reuse, versioning, and security in the .NET Framework. An assembly consists of one or more modules. Each module may contain either a managed code PE file with MSIL code and metadata, or it may contain resources like JPEG or BMP files. One of the managed code PE files must contain a manifest. The manifest is part of an assembly's metadata, it contains the following information:
Versioning information about the assembly, such as its version number, culture information, the publisher's public key, and some additional flags.
An entry for each file that constitutes the assembly that contains the file name, but not the path, of each file in the assembly, a hash value of the file's contents, and some additional flags.
An entry for each publicly exported type in the assembly that tells the runtime which file in the assembly contains the type.
An entry for each resource that tells the runtime which resource file in the assembly contains a particular resource.
Figure 33 shows a schematic view of a single file assembly and a multifile assembly.
In the single file case, the header, the metadataincluding the manifestand the MSIL code are all contained in a file called singlefile.dll. In the multi file case, there are a total of 3 files: multifile1.dll, multifile2.dll, and multifile3.dll. Multifile1.dll is the most important file in the assembly because it contains the manifest for the assembly; it also contains MSIL code and metadata. Multifile2.dll contains MSIL code and metadata also, but notice that it does not contain a manifest. The last file in the multi file assembly, multifile3.dll, contains images and other resources.
Figure 33 A multifile assembly.
To a client that is using an assembly, the fact that the assembly may consist of multiple files is completely hidden. The CLR uses information in the assembly's metadata to create a logical single-file illusion for the consumers of the assembly. Only the developer of the assembly needs to be aware that the physical implementation of the assembly consists of multiple files. When you want to use the types in an assembly, you simply reference the file that contains the manifest. (You can consider this to be the main module of the assembly although it isn't officially labeled as such.) When you first use a type or resource that is not contained in the main file, the CLR will use the information in the manifest to load the module that contains the type or resource. Before it loads the file, the CLR will verify that the module that it is loading is exactly the same as the module that the assembly was built with. It does this using information in the manifest. For each module in the assembly, the manifest stores the file name and a hash value that is generated from the contents of the file. The CLR will regenerate the hash value from the module that resides on disk and compare it to the hash value that is stored in the manifest for that module. If the two hash values do not match, the module that currently resides on disk is not the same as the module that the assembly was originally built with, and the CLR will throw an error. This process guarantees the atomicity of the assembly. In other words, it guarantees that the assembly is deployed and versioned as an indivisible whole. You cannot, for instance, fix a bug by deploying a new version of just one of the modules in an assembly. If you want to make a change to any part of an assembly, you must rebuild and redeploy the entire assembly.
You are hopefully starting to get the idea that an assembly has two views: (1) a logical view and (2) a physical view. The physical view of an assembly is a collection of files that are either managed code module files that contain metadata and MSIL code or resources files that contain JPEGs, Graphic Interchange Formats (GIFs), or other graphical elements like menus or icons. One of these managed code files contains a manifest. The logical view of an assembly is a collection of types in one or more namespaces. In this logical view, the assembly is a single, atomic whole.
By now you're probably asking why Microsoft decided to create this new assembly concept. What advantages does it have over the Win32 approach in which the unit of deployment, versioning, and security is a single executable (DLL or EXE) file? The major advantage of the assembly concept is that it is far more efficient in the scenario where code is downloaded across the Internet. The assembly concept allows you to take rarely used types and resources and package them into separate files that will only be downloaded from the Internet if they are needed. Whether you are dealing with a code library or a functional application, it is very rare for someone to use all of the capabilities of a piece of code during each use.
If you are still confused about the whole concept of an assembly, keep in mind that most assemblies that you create will consist of a single file. In fact, Visual Studio .NET only supports the creation of a single-file assembly. If you want to create a multi file assembly, you have to use the command-line tools in the .NET Framework SDK.
An Example: Building an Assembly
In order for you to gain a better understanding of assemblies, let's build a very simple example that shows you how to create a multi-file assembly. The example is a simple assembly that contains two main types: an Employee class and a Manager class that derives from the Employee class. Each of these types will reside in its own module. The third file will contain only the manifest for the assembly. A schematic of the assembly is shown in Figure 34.
Figure 34 An example multifile assembly.
Because Visual Studio .NET does not support multi-file assemblies, you will build this assembly using the command-line tools in the .NET Framework SDK. You will also use this assembly as a running example throughout the chapter to discuss other topics like metadata and MSIL.
A Summary of the Steps
To build an assembly, you will perform the following steps:
Declare an Employee class.
Declare a Manager class.
Compile the Employee class into a module.
Compile the Manager class into a module.
Compile the modules with an assembly info file (that contains metadata for the assembly) into an assembly.
Create a client application that uses the assembly.
Declare an Employee Class
The Employee class is declared as follows:
using System; namespace metadatademo { public class Employee { public Employee(int id,string name, decimal salary) { this.mName=name; this.mID=id; this.mSalary=salary; } public int ID { get { return mID; } set { mID=value; } } public string Name { get { return mName; } set { mName=value; } } public virtual decimal GetSalary() { return mSalary; } private int mID; private string mName; private decimal mSalary; } }
This class is very simple. It's essentially the same definition that I used earlier when I talked about reference types, so I won't go into an in-depth discussion of this class here. If there are things that you don't understand about this definition like the use of the "using" and "namespace" keywords or the declaration of the "Name" property, don't worry about it. These things will be clearer later after I talk about C# in Chapter 4. Save this code in a file called Employee.cs.
Declare a Manager Class
The Manager class is declared as follows:
using System; namespace metadatademo { public class Manager : Employee { public Manager(int id,string name, decimal salary,decimal bonus) : base(id,name,salary) { this.mBonus=bonus; } public override decimal GetSalary() { return base.GetSalary()+mBonus; } private decimal mBonus; } }
Again, this is a very simple class. Don't worry about the syntax, such as the use of the "base" and "override" keywords; their use will be clear after Chapter 4. Save this code in a file called Manager.cs.
Compile the Employee Class into a Module
To compile the Employee class into a module, open up a command prompt. If you have Visual Studio .NET installed, select Start | Programs | Microsoft Visual Studio .NET | Visual Studio .NET Tools | Visual Studio .NET Command Prompt. This creates a command prompt session that is guaranteed to have the environment (path and so forth ) configured properly for using the command-line tools in the .NET Framework SDK. Now change directories to the directory where you saved the Employee.cs and Manager.cs files and enter the following command at the prompt:
csc /out:Employee.mod /t:module Employee.cs
This command will run the C# compiler (csc). The /out parameter allows you to specify the name of the output file, which in this case is Employee.mod. There is nothing significant about the .mod extension; you could give this file any extension you want. The /t parameter is short for "target," and it's how you specify the type of output you want the compiler to produce. In this case, I want a module file to be produced, which is a file that will be combined with other files to create an assembly. Some of the other possible values for the /t parameter to the C# compiler are the following:
exeCreates a console executable assembly.
winexeCreates a windows executable assembly.
libraryCreates a DLL assembly.
moduleCreates a module that must be combined with other files to create an asssembly.
Compile the Manager Class into a Module
To compile the Manager class into module, run the following command in the same command prompt that you used to compile the Employee class:
csc /out:Manager.mod /t:module /addmodule:Employee.mod Manager.cs
Notice that, in this case, I specified that the output file is called Manager.mod and that the output type is a module. The /addmodule parameter requires some explanation though. You cannot compile the Manager class without referring to the module that contains the Employee class. The compiler must have access to the definition of the Employee class because the Manager derives from the Employee class. You use the /addmodule parameter when you want to create modules that are dependant on types in other modules or when you are combining modules into an assembly. In this case, if you did not use the /addmodule:Employee.mod parameter, you would receive the following error at compile time:
Manager.cs (5,25): error CS0246: The type of namespace name 'Employee' could not be found.
Compile the Modules into an Assembly
We now have our Employee and Manager types compiled into modules, but, at this point, they are still not usable. In order to use these types, they must be built into an assembly with a manifest. To create an assembly, I use the C# compiler to create a third file that contains a manifest that refers to the Employee and Manager modules. To do this, run the following command in the same command prompt that you used to compile the Employee and Manager classes:
csc /out:multifile.dll /t:library /addmodule:employee.mod /addmodule:manager.mod assemblyinfo.cs
In this case, I specify the name of the assembly as multifile.dll and the type of the output as library, which means I am creating a DLL-based assembly. I then use the /addmodule parameter to link the employee and manager modules into the assembly.
Before you can understand the other source file that appears in this command, assemblyinfo.cs, I have to discuss attributes first.
Attributes
Managed code compilers automatically add various metadata elements without you, the developer, having to specify explicitly that the metadata should be added or without having to specify what the value of that metadata element should be. An example of this is the hash value that is stored for each module in an assembly. There are other kinds of metadata for which developers must have some explicit control. Some examples of this type of metadata include:
Whether a class requires a transaction when it runs
Whether a method in a class is exposed through a Web service or the version number of an assembly
Whether an input parameter to a method is an input, an output, or both.
You can explicitly declare and set the values for certain kinds of metadata using attributes. There are pre-defined attributes that you can set values for. You can also create user-defined attributes. Some of the pre-defined attributes provide information that may be used by client code or code generators. Other attributes influence the behavior of the CLR, such as the AssemblyFlagsAttribute that you can use to specify the allowable side-by-side execution behavior of an assembly. Some attributes influence the way a class behaves. For instance, when a class is adorned with the Transaction attribute and the TransactionOption parameter for this attribute is TransactionOption.Required, the CLR and the COM+ runtime will make sure that a Distributed Transaction Coordinator (DTC) transaction is started whenever you call a method on an instance of the class.
In addition, you can create your own attributes. Users can apply your user-defined attributes to your types, and then, at runtime, your code can query for the values that the user has assigned to the attribute and take appropriate action.
Attributes provide support for the declarative programming style that was first popularized with COM (in the way it supports threading models using the ThreadingModel registry key) and MTS. You can support and configure functionality simply by setting an attribute that is associated with a type.
To specify a value for an attribute in C#, you use the following syntax:
[attributename]
An example is shown here:
[Transaction] public class Books : ServicedComponent { // }
This code is using the Transaction attribute from the System.EnterpriseServices namespace. You use this attribute to specify that a class requires transaction support from the COM+ runtime infrastructure.
NOTE
You will learn more about the Transaction Attribute, serviced components, and COM+ integration in Chapter 9.
Each attribute is a class that derives from the Attribute class in the System namespace. In this case, the Transaction attribute is a class called TransactionAttribute, which resides in the System.EnterpriseServices namespace. An attribute may have parameters on its constructor that allow you to specify how the attribute behaves. An example of this is the TransactionOption parameter of the Transaction attribute. This parameter may be set to one of the four possible values of the TransactionOption enumeration shown in Table 34.
Table 34 Possible values for the TransactionOption parameter of the transaction attribute
TransactionOption Value |
Description |
Disabled |
Ignores any transactions in the current context. |
NotSupported |
Will neither start a transaction nor participate in its caller's transaction. |
Supported |
Will not start a transaction, but will share its caller's transaction if it has one. |
Required |
Will share its caller's transaction if it has one; starts a new transaction if the caller does not have a transaction. |
RequiresNew |
Always starts a new transaction. |
The default value of this parameter is Required, so the code I showed with the Books class could have been writtenequivalentlyas follows:
[Transaction(TransactionOption.Required)] public class Books : ServicedComponent { // }
If you want to set the TransactionOption parameter to Supported, you could use the following code:
[Transaction(TransactionOption.Supported)] public class Books : ServicedComponent { // }
Attributes can also have named parameters. For instance, the Transaction attribute has Timeout and Isolation parameters. You can use the Timeout parameter to specify the maximum duration of the transaction before it times out. The Isolation parameter allows you to specify the isolation level for the transaction.
NOTE
I will discuss transactions and isolation levels in Chapter 9.
In the following example, I have set the timeout to 45 seconds and the isolation level to Repeatable Read.
[Transaction(TransactionOption.Required, Isolation=TransactionIsolationLevel.RepeatableRead, Timeout=45)] public class Books : ServicedComponent { // }
The generalized form of an attribute declaration in C# is as follows:
[attributename(param1,param2,,paramN,prop1=val1, prop2=val2,, propN=valN)]
Where param[N] is the specified value for a parameter and prop[N] is the name of a property and val[N] is the value assigned to that property.
Attributes can be applied to an assembly; a module; or any type, such as a class, structure, enum, or delegate; they can also be applied to any member of a type, such as a field, property, method, and so forth. All attributes have a usage attribute that specifies which elements the attribute can be applied to. The Transaction attribute can only be applied to a class. The following code uses the WebMethod attribute from the System.Web.Services namespace to specify that a method will be exposed using a Web service. The WebMethod attribute can only be applied to a method.
[WebMethod] public DataSet GetBooksByTitle(string title) { // }
If you tried to apply the WebMethod attribute to a class as shown here:
[WebMethod] public class Books : ServicedComponent { // }
you will receive the following compiler error:
Attribute 'WebMethod' is not valid on this declaration type. It is valid on 'method' declarations only.
The .NET Framework contains a special set of attributes called assembly attributes that were developed to provide information about an assembly as opposed to types or members of a type. These assembly attributes are divided into four groups: identify attributes, informational attributes, manifest attributes, and strong name attributes. The assembly identity attributes are shown in Table 3-5.
Table 35 The assembly identity attributes
Identity Attribute |
Description |
AssemblyVersion |
This is a numerical version number in the format major.minor.build.revision. |
AssemblyCulture |
If this attribute is set to something other than the empty string (""),it indicates that this is a satellite assembly that contains culture-specific resources. |
AssemblyFlags |
This is used to specify whether an assembly supports side-by-side execution on the same machine, in the same process, or in the same application domain. |
The assembly informational attributes are shown in Table 36.
Table 36 The assembly informational attributes
Informational Attribute |
Description |
AssemblyCompany |
Specifies the name of the company that produced the assembly. |
AssemblyCopyright |
Specifies copyright information for the assembly. |
AssemblyFileVersion |
Specifies the version number that will appear in the Win32 file version resource. It defaults to the assembly version, but does not have to be the same. |
AssemblyInformationalVersion |
Specifies version number information that is not used by the runtime. |
AssemblyProduct |
Specifies product information. |
AssemblyTrademark |
Specifies trademark information. |
The assembly manifest attributes are shown in Table 37.
Table 37 The assembly manifest attributes
Manifest Attribute |
Description |
AssemblyConfiguration |
Specifies the configuration of the resource, for example, retail or debug. |
AssemblyDefaultAlias |
Specifies a friendly name to use for the assembly if the name of the resource is not friendly. |
AssemblyDescription |
Specifies a short description of the purpose and description of the assembly. |
AssemblyTitle |
Specifies another friendly name for the assembly. |
The strong name attributes are shown in Table 38.
Table 38 The assembly strong name attributes
Identity Attribute |
Description |
AssemblyDelaySign |
A Boolean value that indicates whether you want to reserve space in the assembly for a signature, but delay actual signing until later (true) or whether you will be signing the assembly with the private key immediately (false). Using this attribute makes it easier for a company to safeguard its private key by not requiring all developers to have it. |
AssemblyKeyFile |
Specifies the name of the file that contains the public (or public and private) key. |
AssemblyKeyName |
Specifies the key container that contains the key pair. |
You can assign these assembly attributes to an assembly using the standard attribute declaration prefixed with assembly: as follows:
[assembly: AttributeName(param1,param2,,paramN,prop1=val1, prop2=val2,, propN=valN)]
You can create an assembly without using any of these attributes. However, the assembly will not have a strong name, description, or title, and its version number will be 0.0.0.0. Therefore, in most cases, you will want to use at least some of these attributes when you create your assemblies. You can declare these attributes in any source file of the assembly that you want, but a good convention (and the convention used by Visual Studio .NET) is to create an assembly info file that contains all the assembly-level attribute declarations. You then compile this source code file into your assembly.
With that, I can move the discussion back to the assembly info file in the example. I created a file called assemblyinfo.cs for the example, and this file will contain the assembly attributes for the assembly. The contents of this file are as follows:
using System.Reflection; [assembly: AssemblyDescription("A test multifile assembly")] [assembly: AssemblyVersion("1.2.3.4")] [assembly: AssemblyDelaySign(false)] [assembly: AssemblyKeyFile("mykey.snk")]
In this file, I start by adding a using statement for the Reflection namespace in the System assembly because all of the assembly attributes are declared in that namespace. I use the AssemblyDescription attribute to add a description for the assembly. The description is "A test multifile assembly". I use the AssemblyVersion attribute to set the version number of the assembly to "1.2.3.4". These four numbers are the major, minor, build, and revision number, respectively. Before I talk about the AssemblyDelaySign and AssemblyKeyFile attributes I first need to discuss how to create a cryptographic key pair.
The .NET Framework SDK contains a tool called the Strong Name Tool (sn.exe) that you can use to create a cryptographic key pair. To use the Strong Name Tool, open a Visual Studio .NET command prompt by selecting Programs | Microsoft Visual Studio .NET | Visual Studio .NET Tools | Visual Studio .NET Command Prompt from the Start menu, change directories to the location where you created the assemblyinfo.cs file and then enter the following command.
sn k mykey.snk
This command will create a file called "mykey.snk" that contains both the public and private key of a key pair. Notice that we specified the "mykey.snk" file in the AssemblyKeyFile attribute to indicate that we wish to sign the assembly with this key pair. We also use the AssemblyDelaySign attribute to indicate that we are signing the assembly immediately with the private key specified in the AssemblyKeyFile attribute.
Create a Client Application that Uses the Assembly
To test the assembly, I create a very simple console application that uses the Manager and Employee classes. The code for this test application is as follows:
using System; using metadatademo; class TestClass { static void Main(string[] args) { Employee emp1, emp2; emp1=new Employee(1,"Alan Gordon",500); emp2=new Manager(2,"Tamber Gordon",800,200); System.Console.WriteLine( "Emp1 name = {0}",emp1.Name); System.Console.WriteLine( "Emp2 name = {0}",emp2.Name); } }
To compile this code into an executable application, save the preceding code into a file called testclass.cs and execute the following command at the same command prompt that you used previously to build the multifile assembly:
csc /out:testmultifile.exe /t:exe /r:multifile.dll testclass.cs
To build an application or module that uses types in an assembly, you must use the /reference parameter on the C# compiler, which is usually abbreviated /r, to reference the assembly. The compiler uses the metadata in the referenced assembly to do compile-time validation; it also inserts metadata in the client application that tells the CLR the name, version number, strong name, and so forth of the assembly that the application was built against. The CLR will use this information when you run the application to make sure that it uses the same, or a compatible, version of the assembly.
Try running the client application. It will print the names of two employees: Alan Gordon and Tamber Gordon. You might want to add more code to test the Employee and Manager types further. Another good exercise is to attempt to recompile one of the files in the multifile assembly without rebuilding the entire assembly. This is a definite "no-no" because the CLR will enforce the atomicity of the assembly. If the hash of one of the files that constitutes the assembly is not exactly the same as the hash in the assembly's manifest, the CLR will fail to load the file. I got the following error when I rebuilt the Employee module alone without recompiling the assembly:
System.IO.FileLoadException: The check of the module's hash failed for Employee.mod
Metadata
Now that you understand types, assemblies, and attributes, we are ready to begin an in-depth discussion of .NET metadata. Metadata is key to making software development manageable. Metadata is descriptive information about the types in an executable file or software library. This descriptive information includes the name of each type and the name and type of each member (fields, methods, events, and so forth) of each type. For method and events, this metadata should also include the name and type of each parameter to the method or event as well as the return type of the method or event.
Metadata is not a new concept. A header (".h") file in C/C++ is an example of metadata. If you are a COM programmer, you are probably familiar with type libraries, which are the metadata format for COM.
Metadata is used for lots of purposes. Compilers or runtime environments use it to lay out objects in memory. An example of this is a C++ compiler using the definition of a class in a header file to create the vtable for the class in memory. Metadata is also used by system-level code to implement marshaling, for example, to allow code running in one process to use code that is executing in a different process. An example of this is type-library marshaling in COM. Type library marshaling uses metadata in the type library to serialize method calls into a stream that can be sent across processes or machines. Code generators use metadata to create code. An example of this is the COM support in MFC that includes a Visual C++ code generator that allows you to create a set of ColeDispatchDriver-derived wrapper classes for a COM server from the type definitions in its type library. Metadata is also used by compilers to do compile-time error checking that would have to be deferred until runtime if metadata was not available. This error checking includes verifying that a method that you are calling on an object is actually a member of the object's class with the appropriate access and that the method is being called with the right number and types of arguments. Most programmers also use metadata as their first level of documentation. This is especially true now that most IDEs support Intellisense technology. Intellisense uses metadata to automatically display, in the programmer's editor, the complete list of available methods that may be called on an object as soon as the developer types in the method invocation symbol ("." or "->") after a variable name. Intellisense works by interrogating the metadata associated with the types that you are using.
There are a number of problems with COM type libraries that .NET metadata rectifies. First, a type library may or may not be embedded within the executable code that it describes. Most ActiveX controls have their type library embedded as a resource in their .ocx file, but the type library for Office applications (Excel, Word, and so forth) resides in a separate file. If the metadata is in a separate file, there is always a danger that it will become separated from the code that it describes or that someone will accidentally ship version X of the type library with version Y of a COM server. Another problem with COM type libraries is that they aren't guaranteed to be present. Most COM components that I have seen ship with a type library, but the COM specification does not require it.
.NET metadata fixes all of these problems. The metadata for a .NET assembly is always embedded within the assembly. A managed code compiler must generate both MSIL code and metadata. There is no danger that the assembly and its metadata will be separated or that the assembly will be shipped with the wrong version of its metadata.
The metadata in an assembly consists of a manifest and a set of metadata tables that contain the bulk of the type-specific metadata of the assembly, including descriptive information about each type and each field in each type. Regardless of how many files an assembly contains, it will only have one manifest, which may be embedded in one of the modules that comprise the assembly or it may reside in its own file.
The manifest is probably the most important single piece of metadata. The manifest is the only assembly file that a consumer of the assembly has to reference. The manifest contains a directory or index that contains all the information that the CLR needs to locate all the types that are implemented within an assembly, regardless of which module they reside in. The other files in the assembly are loaded on demand only when you use a type that is implemented in one of those files. A high-level listing of the contents of a manifest is the following:
Versioning information about the assembly, such as its version number, culture information, the publisher's public key, and some additional flags
An entry for each file that makes up the assembly that contains the file name, but not the path, of each file in the assembly and a hash value of the file's contents
An entry for each publicly exported type in the assembly that tells the runtime which module in the assembly contains the implementation of the type
An entry for each resource that tells the runtime which file in the assembly contains a particular resource
An entry for each external assembly on which the assembly depends that contains the name, version number, public key, and hash value of the referenced assembly
Let's look at the manifest in the multifile assembly that you created earlier. In this case, the manifest is the sole contents of the file called multifile.dll. You can view the manifest for an assembly and all the other metadata for a .NET assembly using the MSIL disassembler (ILDasm). See the sidebar on ILDASM to learn more about this powerful tool.
The MSIL Disassembler (ILDASM)
ILDASM is a tool that is bundled with the .NET Framework that allows you to view both the compiled MSIL code and the metadata within an assembly. After you have installed Visual Studio .NET, the easiest way to use ILDASM is to select Programs | Microsoft Visual Studio .NET | Visual Studio .NET Tools | Visual Studio .NET Command Prompt. This gives you a command prompt that is configured with the environment that you need to run any of the command-line tools in the .NET Framework SDK. Now you can run ILDASM by typing "ildasm [space] assemblyname". You can also fire up ILDASM without loading an assembly and then choose File | Open. ILDASM also supports drag and drop. Use the command line "ildasm /?" to view the command-line options for ILDASM. You can also find documentation about ILDASM in the .NET Framework SDK documentation at \Tools and Debuggers\.NET Framework Tools\. Curiously, neither this documentation or the /? command say anything about the /adv argument for ILDASM, which makes the tool display more verbose information. To find out about this option, you have to go to a rather small Word file in the Tool Developers Guide documentation, which you can find at [Root directory for Visual Studio .NET installation]\FrameworkSDK\Tool Developers Guide\docs\ILDasmAdvancedOptions.doc. If you run ILDASM with the "/adv" parameter as follows:
Ildasm /adv assemblyname
It will add three new items to the View menu:
COR Header, which displays the header for an assembly. The header contains both the PE header and the CLR header.
Statistics, which allow you to see the sizes of the various portions of the assembly, such as the PE header, the CLR header, the metadata, and the managed code.
Metainfo, which contains several submenu items, the most important of which is the Show! Item, which allows you to view the metadata tables in their "raw" form
The main window of ILDASM is shown in Figure 35.
Figure 35 The main window of ILDASM.
John Robbins has an excellent article in the May, 2001 edition of MSDN magazine that will explain more than you probably want to know about ILDASM and MSIL. See his bugslayer column in that issue.