15 Defining and Referencing Fields


Fields are typed memory locations that store the data of a program. The CLI allows the declaration of both instance and static fields. While static fields are associated with a type and shared across all instances of that type, instance fields are associated with a particular instance of that type. When instantiated, the instance has its own copy of that field.

The CLI also supports global fields, which are fields declared outside of any type definition. Global fields shall be static.

A field is defined by the .field directive (see Partition II, section 21.15).

<field> ::= .field <fieldDecl>

<fieldDecl> ::=

 

[[ <int32> ]] <fieldAttr>* <type> <id> [= <fieldInit> | at <dataLabel>]

The <fieldDecl> has the following parts:

  • An optional integer specifying the byte offset of the field within an instance (see Partition II, section 9.7). If present, the type containing this field shall have the explicit layout attribute. An offset shall not be supplied for global or static fields.

  • Any number of field attributes (see Partition II, section 15.2)

  • Type

  • Name

  • Optionally either a <fieldInit> form or a data label

Global fields shall have a data label associated with them. This specifies where, in the PE file, the data for that field is located. Static fields of a type may, but do not need to, be assigned a data label.

 
 Example (informative): .field private class [.module Counter.dll]Counter counter 

15.1 Attributes of Fields

Attributes of a field specify information about accessibility, contract information, [and] interoperation attributes, as well as information on special handling.

The following subsections contain additional information on each group of predefined attributes of a field.

<fieldAttr> ::=

Description

Section in Partition II

 

assembly

Assembly accessibility.

15.1.1

| famandassem

Family-and-assembly accessibility.

15.1.1

| family

Family accessibility.

15.1.1

| famorassem

Family-or-assembly accessibility.

15.1.1

| initonly

Marks a constant field.

15.1.2

| literal

Specifies a metadata field. No memory is allocated at runtime for this field.

15.1.2

| marshal(<nativeType>)

Marshalling information.

15.1.3

| notserialized

Field is not serialized with other fields of the type.

15.1.2

| private

Private accessibility.

15.1.1

| compilercontrolled

Compiler-controlled accessibility.

15.1.1

| public

Public accessibility.

15.1.1

| rtspecialname

Special treatment by runtime.

15.1.4

| specialname

Special name for other tools.

15.1.4

| static

Static field.

15.1.2

15.1.1 Accessibility Information

The accessibility attributes are assembly, famandassem, family, famorassem, private, compilercontrolled, and public. These attributes are mutually exclusive.

Accessibility attributes are described in Partition II, section 8.2.

15.1.2 Field Contract Attributes

Field contract attributes are initonly, literal, static, and notserialized. These attributes may be combined. Only static fields may be literal. The default is an instance field that may be serialized.

static specifies that the field is associated with the type itself rather than with an instance of the type. Static fields can be accessed without having an instance of a type e.g., by static methods. As a consequence, a static field is shared, within an application domain, between all instances of a type, and any modification of this field will affect all instances. If static is not specified, an instance field is created.

initonly marks fields which are constant after they are initialized. These fields may only be mutated inside a constructor. If the field is a static field, then it may be mutated only inside the type initializer of the type in which it was declared. If it is an instance field, then it may be mutated only in one of the instance constructors of the type in which it was defined. It may not be mutated in any other method or in any other constructor, including constructors of subclasses.

NOTE

The VES need not check whether initonly fields are mutated outside the constructors. The VES need not report any errors if a method changes the value of a constant. However, such code is not valid and is not verifiable.


ANNOTATION

The standard completely specifies the file format, and the serializable bit is in the file format, although serialization was not standardized. Implementing serialization would allow such things as remoting, interactive Web services, saving data in persistent format, etc.

Implementation-Specific (Microsoft): notserialized specifies that this field is not serialized when an instance of this type is serialized. It has no meaning on global or static fields, or if the type does not have the serializable attribute.


literal specifies that this field represents a constant value; they [literal fields] shall be assigned a value. In contrast to initonly fields, literal fields do not exist at runtime. There is no memory allocated for them. literal fields become part of the metadata but cannot be accessed by the code. literal fields are assigned a value by using the <fieldInit> syntax (see Partition II, section 15.2).

NOTE

It is the responsibility of tools generating CIL to replace source code references to the literal with its actual value. Hence changing the value of a literal requires recompilation of any code that references the literal. Literal values are, thus, not version-resilient.


15.1.3 Interoperation Attributes

There is one attribute for interoperation with pre-existing native applications; it is platform-specific and shall not be used in code intended to run on multiple implementations of the CLI. The attribute is marshal and specifies that the field's contents should be converted to and from a specified native data type when passed to unmanaged code. Every conforming implementation of the CLI will have default marshalling rules, as well as restrictions on what automatic conversions can be specified using the marshal attribute. See also Partition II, section 14.5.5.

NOTE

Marshaling of user-defined types is not required of all implementations of the CLI. It is specified in this standard so that implementations which choose to provide it will allow control over its behavior in a consistent manner. While this is not sufficient to guarantee portability of code that uses this feature, it does increase the likelihood that such code will be portable.


15.1.4 Other Attributes

The attribute rtspecialname indicates that the field name shall be treated in a special way by the runtime.

RATIONALE

There are currently no field names that are required to be marked with rtspecialname. It is provided for extensions, future standardization, and to increase consistency between the declaration of fields and methods (instance and type initializer methods shall be marked with this attribute).


The attribute specialname indicates that the field name has special meaning to tools other than the runtime, typically because it marks a name that has meaning for the Common Language Specification (CLS; see Partition I, sections 8.11.3 and 8.11.4).

15.2 Field Init Metadata

The <fieldInit> metadata can be optionally added to a field declaration. The use of this feature may not be combined with a data label.

The <fieldInit> information is stored in metadata, and this information can be queried from metadata. But the CLI does not use this information to automatically initialize the corresponding fields. The field initializer is typically used with literal fields (see Partition II, section 15.1.2) or parameters with default values. See Partition II, section 21.9.

The following table lists the options for a field initializer. Note that while both the type and the field initializer are stored in metadata, there is no requirement that they match. (Any importing compiler is responsible for coercing the stored value to the target field type). The description column in the table below provides additional information.

<fieldInit> ::=

Description

 

bool ( true | false )

Boolean value, encoded as true or false.

| bytearray ( <bytes> )

String of bytes, stored without conversion. May be padded with one zero byte to make the total byte-count an even number.

| char ( <int32> )

16-bit unsigned integer (Unicode character).

| float32 ( <float64> )

32-bit floating point number, with the floating point number specified in parentheses.

| float32 ( <int32> )

<int32> is binary representation of float.

| float64 ( <float64> )

64-bit floating point number, with the floating point number specified in parentheses.

| float64 ( <int64> )

<int64> is binary representation of double.

| [ unsigned ] int8 ( <int8> )

8-bit integer with the integer specified in parentheses.

| [ unsigned ] int16 ( <int16> )

16-bit integer with the integer specified in parentheses.

| [ unsigned ] int32 ( <int32> )

32-bit integer with the integer specified in parentheses.

| [ unsigned ] int64 ( <int64> )

64-bit integer with the integer specified in parentheses.

| <QSTRING>

String. <QSTRING> is stored as Unicode.

| nullref

Null object reference.

ANNOTATION

Implementation-Specific (Microsoft): ilasm does not recognize the optional unsigned modifier before the int8, int16, int32, or int64 keywords.


 

Example (informative): The following example shows a typical use of this: .field public static literal valuetype ErrorCodes no_error = int8(0) The field named no_error is a literal of type ErrorCodes (a value type) for which no graphics/ccc.gif memory is allocated. Tools and compilers can look up the value and detect that it is graphics/ccc.gif intended to be an 8-bit signed integer whose value is 0.

15.3 Embedding Data in a PE File

There are several ways to declare a data field that is stored in a PE file. In all cases, the .data directive is used.

Data can be embedded in a PE file by using the .data directive at the top level.

<decl> ::=

Section in Partition II

 

.data <datadecl>

6.6

| ...

 

Data may also be declared as part of a type:

<classMember> ::=

Section in Partition II

 

.data <datadecl>

9.2

| ...

 

Yet another alternative is to declare data inside a method:

<methodBodyItem> ::=

Section in Partition II

 

.data <datadecl>

14.4.1

| ...

 

15.3.1 Data Declaration

A .data directive contains an optional data label and the body which defines the actual data. A data label shall be used if the data is to be accessed by the code.

<dataDecl> ::= [<dataLabel> =] <ddBody>

The body consists either of one data item or a list of data items in braces. A list of data items is similar to an array.

<ddBody> ::=

 

<ddItem>

| { <ddItemList> }

A list of items consists of any number of items:

<ddItemList> ::= <ddItem> [, <ddItemList>]

The list may be used to declare multiple data items associated with one label. The items will be laid out in the order declared. The first data item is accessible directly through the label. To access the other items, pointer arithmetic is used, adding the size of each data item to get to the next one in the list. The use of pointer arithmetic will make the application not verifiable. (Each data item shall have a <dataLabel> if it is to be referenced afterward; missing a <dataLabel> is useful in order to insert alignment padding between data items.)

A data item declares the type of the data and provides the data in parentheses. If a list of data items contains items of the same type and initial value, the grammar below can be used as a shortcut for some of the types: the number of times the item shall be replicated is put in brackets after the declaration.

<ddItem> ::=

Description

 

& ( <id> )

Address of label

| bytearray ( <bytes> )

Array of bytes

| char * ( <QSTRING> )

Array of (Unicode) characters

| float32 [( <float64> )] [[ <int32> ]]

32-bit floating point number, may be replicated

| float64 [( <float64> )] [[ <int32> ]]

64-bit floating point number, may be replicated

| int8 [( <int8> )] [[ <int32> ]]

8-bit integer, may be replicated

| int16 [( <int16> )] [[ <int32> ]]

16-bit integer, may be replicated

| int32 [( <int32> )] [[ <int32> ]]

32-bit integer, may be replicated

| int64 [( <int64> )] [[ <int32> ]]

64-bit integer, may be replicated

 
 Example (informative): The following declares a 32-bit signed integer with value 123: .data theInt = int32(123) The following declares 10 replications of an 8-bit unsigned integer with value 3: .data theBytes = int8 (3) [10] 
15.3.2 Accessing Data from the PE File

The data stored in a PE file using the .data directive can be accessed through a static variable, either global or a member of a type, declared at a particular position of the data:

<fieldDecl> ::= <fieldAttr>* <type> <id> at <dataLabel>

The data is then accessed by a program as it would access any other static variable, using instructions such as ldsfld, ldsflda, and so on (see Partition III).

The ability to access data from within the PE file may be subject to platform-specific rules, typically related to section access permissions within the PE file format itself.

 

Example (informative): The following accesses the data declared in the example of Partition II, section 15.3.1. graphics/ccc.gif First a static variable needs to be declared for the data -- e.g., a global static variable: .field public static int32 myInt at theInt Then the static variable can be used to load the data: ldsfld int32 myInt // data on stack
15.3.3 Unmanaged Thread-Local Storage

ANNOTATION

Implementation-Specific (Microsoft): Each PE file has a particular section whose initial contents are copied whenever a new thread is created. This section is called unmanaged thread-local storage. The Microsoft implementation of ilasm allows the creation of this unmanaged thread-local storage by extending the data declaration to include an option attribute, tls:

 
 <dataDecl> ::= [tls] [<dataLabel> =] <ddBody> 

The CLI provides two mechanisms for dealing with thread-local storage (tls): an unmanaged mechanism and a managed mechanism. The unmanaged mechanism has a number of restrictions that are carried forward directly from the underlying platform into the CLI. For example, the amount of thread-local storage is determined when the PE file is loaded and cannot be expanded. The amount is computed based on the static dependencies of the PE file; DLLs that are loaded as a program executes cannot create their own thread-local storage through this mechanism. The managed mechanism, which does not have these restrictions, is part of the Base Class Library.

For unmanaged tls there is a particular native code sequence that can be used to locate the start of this section for the current thread. The CLI respects this mechanism. That is, when a reference is made to a static variable with a fixed RVA (Relative Virtual Address) in the PE file and that RVA is in the thread-local section of the PE, the native code generated from the CIL will use the thread-local access sequence.

This has two important consequences:

  • A static variable with a specified RVA shall reside entirely in a single section of the PE file. The RVA specifies where the data begins, and the type of the variable specifies how large the data area is.

  • When a new thread is created, only the data from the PE file is used to initialize the new copy of the variable. There is no opportunity to run the type initializer. For this reason it is probably wise to restrict the use of unmanaged thread-local storage to the primitive numeric types and value types with explicit layout that have a fixed initial value and no type initializer.


15.4 Initialization of Non-Literal Static Data

This section and its subsections contain only informative text.


Many languages that support static data (i.e., variables that have a lifetime that is the entire program) provide for a means to initialize that data before the program begins running. There are three common mechanisms for doing this, and each is supported in the CLI.

15.4.1 Data Known at Link Time

When the correct value to be stored into the static data is known at the time the program is linked (or compiled, for those languages with no linker step), the actual value can be stored directly into the PE file, typically into the data area (see Partition II, section 15.3). References to the variable are made directly to the location where this data has been placed in memory, using the OS-supplied fixup mechanism to adjust any references to this area if the file loads at an address other than the one assumed by the linker.

In the CLI, this technique can be used directly if the static variable has one of the primitive numeric types or is a value type with explicit type layout and no embedded references to managed objects. In this case the data is laid out in the data area as usual, and the static variable is assigned a particular RVA (i.e., offset from the start of the PE file) by using a data label with the field declaration (using the at syntax).

ANNOTATION

The RVA (Relative Virtual Address) is the relative offset from the beginning of the file as it is loaded in memory. This is not the same as the byte offset of the file. For more information on RVAs, see Chapter 4 of this book.


This mechanism, however, does not interact well with the CLI notion of an application domain (see Partition I, section 12.5). An application domain is intended to isolate two applications running in the same OS process from one another by guaranteeing that they have no shared data. Since the PE file is shared across the entire process, any data accessed via this mechanism is visible to all application domains in the process, thus violating the application domain isolation boundary.

15.5 Data Known at Load Time

When the correct value is not known until the PE file is loaded (for example, if it contains values computed based on the load addresses of several PE files), it may be possible to supply arbitrary code to run as the PE file is loaded, but this mechanism is platform-specific and may not be available in all conforming implementations of the CLI.

ANNOTATION

Implementation-Specific (Microsoft): This mechanism, while available in Microsoft's CLR, is strongly discouraged. The code runs under the processwide loader lock, and the restrictions imposed by the underlying operating system make this a fragile mechanism. The details are provided in Partition II, section 24.3.3.3.


15.5.1 Data Known at Runtime

When the correct value cannot be determined until type layout is computed, the user shall supply code as part of a type initializer to initialize the static data. The guarantees about type initialization are covered in Partition II, section 9.5.3.1. As will be explained below, global statics are modelled in the CLI as though they belonged to a type, so the same guarantees apply to both global and type statics.

Because the layout of managed types need not occur until a type is first referenced, it is not possible to statically initialize managed types by simply laying the data out in the PE file. Instead, there is a type initialization process that proceeds in the following steps:

  1. All static variables are zeroed.

  2. The user-supplied type initialization procedure, if any, is invoked as described in Partition II, section 9.5.3.

Within a type initialization procedure there are several techniques:

  • Generate explicit code that stores constants into the appropriate fields of the static variables. For small data structures this can be efficient, but it requires that the initializer be converted to native code, which may prove to be both a code space and an execution time problem.

  • Box value types. When the static variable is simply a boxed version of a primitive numeric type or a value type with explicit layout, introduce an additional static variable with known RVA that holds the unboxed instance and then simply use the box instruction to create the boxed copy.

  • Create a managed array from a static native array of data. This can be done by marshalling the native array to a managed array. The specific marshaller to be used depends on the native array. For example, it may be a safearray.

  • Default initialize a managed array of a value type. The Base Class Library provides a method that zeroes the storage for every element of an array of unboxed value types (System.Runtime.CompilerServices.InitializeArray).

ANNOTATION

Implementation-Specific (Microsoft): Use Base Class Library deserialization. The Microsoft Base Class Library provides serialization and deserialization services. These services can be found in the System.Runtime.Serialization namespace. These services were not, however, standardized. An object can be converted to a serialized form, stored in the data section, and accessed using a static variable with known RVA of type unsigned int8[]. The corresponding deserialization mechanism can then be used in the type initializer.


End informative text




The Common Language Infrastructure Annotated Standard (Microsoft. NET Development Series)
The Common Language Infrastructure Annotated Standard (Microsoft. NET Development Series)
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 121

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