Assemblies and modules are grouping constructs, each playing a different role in the CLI. An assembly is a set of one or more files deployed as a unit. An assembly always contains a manifest that specifies (see Partition II, section 6.1):
A module is a single file containing executable content in the format specified here. If the module contains a manifest, then it also specifies the modules (including itself) that constitute the assembly. An assembly shall contain only one manifest among all its constituent files. For an assembly to be executed (rather than dynamically loaded) the manifest shall reside in the module that contains the entry point. While some programming languages introduce the concept of a namespace, there is no support in the CLI for this concept. Type names are always specified by their full name relative to the assembly in which they are defined.
6.1 Overview of Modules, Assemblies, and Files
Figure 3-2 should clarify the various forms of references. Figure 3-2. ReferencesEight files are shown in the picture. The name of each file is shown below the file. Files that declare a module have an additional border around them and have names beginning with "M." The other two files have a name beginning with "F." These files may be resource files, like bitmaps, or other files that do not contain CIL code. Files M1 and M4 declare an assembly in addition to the module declaration namely, assemblies A and B, respectively. The assembly declaration in M1 and M4 references other modules, shown with straight lines. Assembly A references M2 and M3. Assembly B references M3 and M5. Thus, both assemblies reference M3. Usually, a module belongs only to one assembly, but it is possible to share it across assemblies. When assembly A is loaded at runtime, an instance of M3 will be loaded for it. When assembly B is loaded into the same application domain, possibly simultaneously with assembly A, M3 will be shared for both assemblies. Both assemblies also reference F2, for which similar rules apply. The module M2 references F1, shown by dotted lines. As a consequence, F1 will be loaded as part of assembly A when A is executed. Thus, the file reference shall also appear with the assembly declaration. Similarly, M5 references another module, M6, which becomes part of B when B is executed. It follows that assembly B shall also have a module reference to M6.
6.2 Defining an Assembly
An assembly is specified as a module that contains a manifest in the metadata; see Partition II, section 21.2. The information for the manifest is created from the following portions of the grammar:
The .assembly directive declares the manifest and specifies to which assembly the current module belongs. A module shall contain at most one .assembly directive. The <dottedname> specifies the name of the assembly. NOTE Since some platforms treat names in a case-insensitive manner, two assemblies that have names that differ only in case should not be declared.
The .corflags directive sets a field in the CLI header of the output PE file [the COMIMAGE_FLAGS_ILONLY flag] (see Partition II, section 24.3.3.1). A conforming implementation of the CLI shall expect it to be 1. For backward compatibility, the three least significant bits are reserved. Future versions of this standard may provide definitions for values between 8 and 65,535. Experimental and non-standard uses should thus use values greater than 65,535. The .subsystem directive is used only when the assembly is directly executed (as opposed to used as a library for another program). It specifies the kind of application environment required for the program, by storing the specified value in the PE file header (see Partition II, section 24.2.2). While a full 32-bit integer may be supplied, a conforming implementation of the CLI need only respect two possible values: If the value is 2, the program should be run using whatever conventions are appropriate for an application that has a graphical user interface. If the value is 3, the program should be run using whatever conventions are appropriate for an application that has a direct console attached.
Example (informative): .assembly CountDown { .hash algorithm 32772 .ver 1:0:0:0 } .file Counter.dll .hash = (BA D9 7D 77 31 1C 85 4C 26 9C 49 E7 02 BE E7 52 3A CB 17 AF) 6.2.1 Information about the Assembly (<asmDecl>)The following grammar shows the information that can be specified about an assembly.
6.2.1.1 Hash Algorithm
When an assembly consists of more than one file (see Partition II, section 6.2.3), the manifest for the assembly specifies both the name of the file and the cryptographic hash of the contents of the file. The algorithm used to compute the hash can be specified, and shall be the same for all files included in the assembly. All values are reserved for future use, and conforming implementations of the CLI shall use the SHA1 hash function and shall specify this algorithm by using a value of 32772 (0x8004). RATIONALE SHA1 was chosen as the best widely available technology at the time of standardization (see Partition I). A single algorithm is chosen, since all conforming implementations of the CLI would be required to implement all algorithms to ensure portability of executable images. 6.2.1.2 Culture
When present, this indicates that the assembly has been customized for a specific culture. The strings that shall be used here are those specified in the .NET Framework Standard Library Annotated Reference as acceptable with the class System.Globalization. CultureInfo. When used for comparison between an assembly reference and an assembly definition, these strings shall be compared in a case-insensitive manner.
NOTE The culture names follow the IETF RFC1766 names. The format is "<language>-<country/region>", where <language> is a lowercase two-letter code in ISO 639-1. <country/region> is an uppercase two-letter code in ISO 3166. 6.2.1.3 Originator's Public Key
The CLI metadata allows the producer of an assembly to compute a cryptographic hash of the assembly (using the SHA1 hash function) and then encrypt it using the RSA algorithm and a public/private key pair of the producer's choosing. The results of this (an "SHA1/RSA digital signature") can then be stored in the metadata along with the public part of the key pair required by the RSA algorithm. The .publickey directive is used to specify the public key that was used to compute the signature. To calculate the hash, the signature is zeroed, the hash calculated, then the result stored into the signature. A reference to an assembly (see Partition II, section 6.3) captures some of this information at compile time. At runtime, the information contained in the assembly reference can be combined with the information from the manifest of the assembly located at runtime to ensure that the same private key was used to create both the assembly seen when the reference was created (compile time) and when it is resolved (runtime). 6.2.1.4 Version Numbers
The version number of the assembly is specified as four 32-bit integers. This version number shall be captured at compile time and used as part of all references to the assembly within the compiled module. This standard places no other requirements on the use of the version numbers. All standardized assemblies shall have the last two 32-bit integers set to 0. This standard places no other requirement on the use of the version numbers, although individual implementers are urged to avoid setting both of the last two 32-bit integers to 0 to avoid a possible collision with future standards. Future versions of this standard shall change one or both of the first two 32-bit integers specified for a standardized assembly if any additional functionality is added or any additional features of the virtual machine are required to implement it. Furthermore, this standard shall change one or both of the first two 32-bit integers specified for the mscorlib assembly so that its version number may be used (if desired) to distinguish between different versions of the Virtual Execution system required to run programs conforming to that version of the standard. NOTE A conforming implementation may ignore version numbers entirely, or it may require that they match precisely when binding a reference, or any other behavior deemed appropriate. By convention: The first of these is considered the major version number, and assemblies with the same name but different major versions are not interchangeable. This would be appropriate, for example, for a major rewrite of a product where backward compatibility cannot be assumed. The second of these is considered the minor version number, and assemblies with the same name and major version but different minor versions indicate significant enhancements but with intention to be backward compatible. This would be appropriate, for example, on a "point release" of a product or a fully backward compatible new version of a product. The third of these is considered the revision number, and assemblies with the same name, major and minor version number, but different revisions are intended to be fully interchangeable. This would be appropriate, for example, to fix a security hole in a previously released assembly. The fourth of these is considered the build number, and assemblies that differ only by build number are intended to represent a recompilation from the same source. This would be appropriate, for example, because of processor, platform, or compiler changes. 6.2.2 Manifest ResourcesA manifest resource is simply a named item of data associated with an assembly. A manifest resource is introduced using the .mresource directive, which adds the manifest resource to the assembly manifest begun by a preceding .assembly declaration.
If the manifest resource is declared public, it is exported from the assembly. If it is declared private, it is not exported and hence only available from within the assembly. The <dottedname> is the name of the resource, and the optional quoted string is a description of the resource.
For a resource stored in a file that is not a module (for example, an attached text file), the file shall be declared in the manifest using a separate (top-level) .file declaration (see Partition II, section 6.2.3) and the byte offset shall be zero. Similarly, a resource that is defined in another assembly is referenced using .assembly extern, which requires that the assembly has been defined in a separate (top-level) .assembly extern directive (see Partition II, section 6.3). 6.2.3 Files in the AssemblyAssemblies may be associated with other files e.g., documentation and other files that are used during execution. The declaration .file is used to add a reference to such a file to the manifest of the assembly (see Partition II, section 21.19).
The attribute nometadata is specified if the file is not a module according to this specification. Files that are marked as nometadata may have any format; they are considered pure data files. The <bytes> after the .hash specify a hash value computed for the file. The VES shall recompute this hash value prior to accessing this file and shall generate an exception if it does not match. The algorithm used to calculate this hash value is specified with .hash algorithm (see Partition II, section 6.2.1.1). If specified, the .entrypoint directive indicates that the entry point of a multi-module assembly is contained in this file.
6.3 Referencing Assemblies
An assembly mediates all accesses from the files that it contains to other assemblies. This is done through the metadata by requiring that the manifest for the executing assembly contain a declaration for any assembly referenced by the executing code. The syntax .assembly extern as a top-level declaration is used for this purpose. The optional as clause provides an alias which allows ilasm to address external assemblies that have the same name, but that differ in version, culture, etc. The dotted name used in .assembly extern shall exactly match the name of the assembly as declared with the .assembly directive in a case-sensitive manner. (So, even though an assembly might be stored within a file, within a file system that is case-blind, the names stored internally within metadata are case-sensitive, and shall match exactly.)
These declarations are the same as those for .assembly declarations (Partition II, section 6.2.1), except for the addition of .publickeytoken. This declaration is used to store the low 8 bytes of the SHA1 hash of the originator's public key in the assembly reference, rather than the full public key. An assembly reference can store either a full public key or an 8-byte "publickeytoken." Either can be used to validate that the same private key used to sign the assembly at compile time signed the assembly used at runtime. Neither is required to be present, and while both can be stored, this is not useful. A conforming implementation of the CLI need not perform this validation, but it is permitted to do so, and it may refuse to load an assembly for which the validation fails. A conforming implementation of the CLI may also refuse to permit access to an assembly unless the assembly reference contains either the public key or the public key token. A conforming implementation of the CLI shall make the same access decision independent of whether a public key or a token is used. RATIONALE The full public key is cryptographically safer, but requires more storage space in the assembly reference. Example (informative): .assembly extern MyComponents { .publickey = (BB AA BB EE 11 22 33 00) .hash = (2A 71 E9 47 F5 15 E6 07 35 E4 CB E3 B4 A1 D3 7F 7F A0 9C 24) .ver 2:10:2002:0 } 6.4 Declaring ModulesAll CIL files are modules and are referenced by a logical name carried in the metadata rather than their file name. See Partition II, section 21.27.
Example (informative): .module CountDown.exe
6.5 Referencing ModulesWhen an item is in the current assembly but part of a different module than the one containing the manifest, the defining module shall be declared in the manifest of the assembly using the .module extern directive. The name used in the .module extern directive of the referencing assembly shall exactly match the name used in the .module directive (see Partition II, section 6.4) of the defining module. See Partition II, section 21.28.
Example (informative): .module extern Counter.dll 6.6 Declarations inside a Module or AssemblyDeclarations inside a module or assembly are specified by the following grammar. More information on each option can be found in the corresponding section.
6.7 Exported Type DefinitionsThe manifest module, of which there can only be one per assembly, includes the .assembly statement. To export a type defined in any other module of an assembly requires an entry in the assembly's manifest. The following grammar is used to construct such an entry in the manifest:
The <exportAttr> value shall be either public or nested public and shall match the visibility of the type. For example, suppose an assembly consists of two modules: A.EXE and B.DLL. A.EXE contains the manifest. A public class "Foo" is defined in B.DLL. In order to export it that is, to make it visible by, and usable from, other assemblies a .class extern statement shall be included in A.EXE. Conversely, a public class "Bar" defined in A.EXE does not need any .class extern statement. RATIONALE Tools should be able to retrieve a single module, the manifest module, to determine the complete set of types defined by the assembly. Therefore, information from other modules within the assembly is replicated in the manifest module. By convention, the manifest module is also known as the assembly. |