Copyright © 2002, 2003 Southern Storm Software, Pty Ltd.
Permission to distribute copies of this work under the terms of the
GNU Free Documentation License is hereby granted.
struct
,
union
, and other special C types may need to be mangled
to conform with CLI conventions, but such mangling should still be
readable to a human debugging the compiler. Where appropriate,
mangling should result in a type name that is accessible from C#.
Note 1. Given the nature of C, it is always possible for a programmer to write code that depends upon platform-specific word sizes, endianness, and operating system facilities. Our goal is that C code written to commonly used C coding standards should not be aware of such platform differences.Some things are deliberately outside the scope of this ABI definition. We do not describe the facilities that are provided by the "libc" implementation, or the contents of standard header files, for example.
Note 2. This doesn't preclude the application programmer from using native code facilities such as PInvoke. But the compiler itself will not use such features to implement the ABI.
Note 3. If the ABI avoids vendor-specific naming, it is more likely to be adopted by other vendors.
In the sections below, we suggest extended syntax for the C language to enable access to CLI-specific features. This syntax is only a suggestion. Two compilers that use different syntax for the same feature can still interoperate if they translate their syntax into the same ABI conventions.
Note: Some of the features described in this document haven't been fully implemented by Portable.NET's C compiler yet. This document is therefore subject to change.
OpenSystem.C
" assembly is assumed to exist in any
system that conforms to this ABI. It provides a number of classes for
tagging C applications and implementing ABI support facilities.
The following list summarises the important classes in the
"OpenSystem.C
" namespace:
ModuleAttribute
IsConst
const
".IsFunctionPointer
IsComplexPointer
BitFieldAttribute
WeakAliasForAttribute
StrongAliasForAttribute
InitializerAttribute
.cctor
methods), in that initializers are
guaranteed to be executed before main
.InitializerOrderAttribute
FinalizerAttribute
Finalize
"
methods in garbage-collected objects. The order of garbage-collected
object finalization is indeterminate with respect to C finalizers.FinalizerOrderAttribute
OriginalNameAttribute
LongJmpException
setjmp
/longjmp
operations.Crt0
FloatComplex
, DoubleComplex
FloatImaginary
, DoubleImaginary
CNameAttribute
FloatComplex
" is marked as
"float _Complex
".GlobalScopeAttribute
ModuleScopeAttribute
To simplify discussion, we will use an abbreviated syntax to describe attributes in CIL assembly code examples. For example, the following two examples are equivalent:
This abbreviation is for exposition purposes only. It isn't intended to suggest an alternative syntax for CIL assemblers..module test.exe .custom instance void [OpenSystem.C]OpenSystem.C.ModuleAttribute::.ctor() = (01 00 00 00) .module test.exe .custom [OpenSystem.C.Module]
OpenSystem.C.ModuleAttribute
":
Linkers can use the presence of this attribute to detect that a C application is being linked, rather than a C# application, and then modify their behaviour accordingly. For example, by adding additional libraries to the link that aren't normally required by C# applications..module test.exe .custom instance void [OpenSystem.C]OpenSystem.C.ModuleAttribute::.ctor() = (01 00 00 00)
Type | Size | Description |
void |
1 1 | Void type |
_Bool |
1 | 8-bit boolean value (C# "bool ") |
char |
1 | Signed 8-bit integer |
unsigned char |
1 | Unsigned 8-bit integer |
short |
2 | Signed 16-bit integer |
unsigned short |
2 | Unsigned 16-bit |
__wchar__ |
2 | 16-bit wide character value (C# "char ") |
int |
4 | Signed 32-bit integer |
unsigned int |
4 | Unsigned 32-bit integer |
long |
4/8 2 | Signed 64-bit or 32-bit integer |
unsigned long |
4/8 2 | Unsigned 64-bit or 32-bit integer |
long long |
8 | Signed 64-bit integer |
unsigned long long |
8 | Unsigned 64-bit integer |
float |
4 | 32-bit IEEE 754 floating-point |
double |
8 | 64-bit IEEE 754 floating-point |
long double |
8 | Same as double |
type * |
4/8 3 | Pointer to "type " |
float _Complex |
8 | Complex number type based on float |
double _Complex |
16 | Complex number type based on double |
long double _Complex |
16 | Same as double _Complex |
float _Imaginary |
4 | Imaginary number type based on float |
double _Imaginary |
8 | Imaginary number type based on double |
long double _Imaginary |
8 | Same as double _Imaginary |
Note 1. The size of "void
" is 1, to be
consistent with gcc.
Note 1. The "long
" type is represented by
the IL "native int
type so that it is the same size as
pointers on the underlying platform. The "unsigned long
"
type is represented by "native unsigned int
" for a
similar reason. The size of these types is determined at runtime.
Note 3. The size of pointer types is determined at runtime.
The CLI specifications describe a primitive "native float
" type
which would be the ideal representation for "long double
".
However, early versions of Microsoft's commercial and Rotor CLR
implementations contained a bug that prevented "native float
"
from being used in field or method signatures. For interoperability with
these early CLR's, we have chosen to use "long double
"
as a synonym for "double
".
const
" and "volatile
" qualifiers are
represented using the "OpenSystem.C.IsConst
" and
"System.Runtime.CompilerServices.IsVolatile
" modifiers.
The following table provides some examples:
Declaration | Representation |
const int x; |
int32 modopt(OpenSystem.C.IsConst) x |
void * volatile y; |
void * modreq(System.Runtime.CompilerServices.IsVolatile) y |
const char *s; |
int8 modopt(OpenSystem.C.IsConst) * s |
char * const s; |
int8 * modopt(OpenSystem.C.IsConst) s |
The placement of the type modifier is important. A qualifier at the
outer-most level of a type applies to the field or variable. A qualifier
at an inner level applies to a referenced type. In the last example
above, the variable "s
" cannot be modified, but it points
at a string that can be modified. In the second last example, the
variable can be modified, but not the string.
The "IsVolatile
" modifier is required, to be consistent
with other CLI-compatible languages. The "IsConst
" modifier
is optional, because other CLI-compatible languages can safely ignore it
(the programmer on the other hand probably shouldn't ignore it).
Fixed types have a constant size. The expression "sizeof(T)
"
can be evaluated to a constant at compile time. An example is
"sizeof(int)
", which is always 4.
Dynamic and complex types have a constant size, but these values
are not known until runtime. An example of a dynamic type is
"long
", whose size may be either 4 or 8 depending
upon the runtime platform.
Dynamic and complex types are distinguished by the difficulty of
determining the size of the type at runtime. Dynamic types can
use the IL "sizeof
" instruction, but complex types
must compute the size through manual measurement and alignment
of type members.
Unknown types have no known size. An example is "char[]
",
which cannot be used as the type of a structure field, as its storage
size cannot be determined.
Traditional C compilers only have "fixed" and "unknown" types.
[Historical note: an earlier version of this ABI tried to make as many types "fixed" as possible, using explicit layout for everything. This was successful, but very wasteful of memory on 32-bit platforms.]
void
",
"_Bool
", "char
", "unsigned char
",
"__whar__
", "short
", "unsigned short
",
"int
", "unsigned int
",
"long long
", "unsigned long long
",
"float
", and "double
".
The primitive C types "long
" and "unsigned long
",
are dynamic, as they are based on the "System.IntPtr
" and
"System.UIntPtr
" types, which have an unknown size at
compile time.
Enumerated types and bit fields are fixed or dynamic based on the category of the primitive integer type upon which they are based.
Pointer types are always dynamic. This includes function pointers
and object references. At runtime, "sizeof System.IntPtr
"
can be used to intuit the platform's actual pointer size.
Any structure, union, or array type with an unknown element is itself unknown.
Structure types with at least one complex field are complex. Structure types with at least one dynamic field (but no complex fields) are dynamic.
Structure types whose fields are all fixed may also be fixed if the start of each field exactly matches the end of the previous field using the default alignment rule of "a type's alignment is the same as its size". i.e. default alignment must not leave any padding gaps. This includes the gap at the end that results from aligning the entire structure. Padding gaps cause the structure to be labelled dynamic.
As an example, the first structure below is fixed because there are no default alignment padding gaps. The second structure is dynamic because default alignment will leave a padding gap of 4 bytes at the end:
struct A { long long x; int y; int z; }; struct B { long long x; int y; };
Union types with at least one complex field are complex. Union types with at least one dynamic field (but no complex fields) are dynamic. All other union types are fixed.
An array whose element type is fixed and whose size expression is a compile time constant, is fixed. All other arrays are complex. The first example below is fixed and the other two are complex:
char a[100]; char b[sizeof(void *)]; void *c[100];
The size of a dynamic type is evaluated at runtime using the IL
"sizeof
" instruction.
The size of a complex type is evaluated at runtime by emitting a
"ldsfld
" instruction for the read-only static field
"type::'size.of'
". The compiler is responsible for
emitting a static constructor that computes the actual size and
places it into this field (more information on this is given in
later sections).
If the compiler needs to determine the alignment of a type "T
",
it emits a value type definition of the following form:
.class public sequential sealed ansi 'align T' extends System.ValueType { .field public int8 pad .field public T value }
Then the alignment value can be obtained with the following IL code:
ldc.i4.0 conv.i ldflda T 'align T'::value conv.u4
The result is an unsigned 32-bit value corresponding to the number of bytes
that are needed to align "T
" correctly. Optimizing CLR's
will fold the above sequence into a simple constant value.
The "OpenSystem.C.Crt0
" type provides a helper method
that can assist with aligning complex structure types. The method
has the following prototype:
public static uint Align(uint size, uint flags);
The "Crt0.Align
" method aligns the "size
"
value according to the supplied alignment "flags
":
0x0001
byte
" fields.0x0002
0x0004
0x0008
0x0010
0x0020
short
" fields.0x0040
int
" fields.0x0080
long long
" fields.0x0100
float
" fields.0x0200
double
" fields.0x0400
long
" fields.
The alignment flag set for a structure or union type is the bitwise OR of
the flag sets for each of its member fields. The "Crt0.Align
"
method chooses the largest alignment value for all of the supplied flags.
If no flags are supplied, then byte alignment is assumed.
The first five flag values typically result from the compiler explicitly
measuring an "align T
" type with the code presented above.
The other flag values are shortcuts: the compiler can avoid measuring
primitive types and just pass the corresponding flag. The
"Crt0.Align
" method performs the primitive type
measurement itself.
The integer and floating-point types of the same size use different flags on purpose, because the FPU might use different alignment rules than the integer CPU. On some systems, 64-bit integers might be alignable on 32-bit boundaries, but 64-bit floating point values still need 64-bit alignment.
struct A
") are converted into a value
type called "A
", with no namespace qualifier. This
value type is marked as having sequential layout:
Anonymous structures are assigned a unique value based on hashing the fields within the structure using the MD5 hash algorithm:struct A { int item; struct A *next; }; .class public sequential serializable sealed ansi 'A' extends System.ValueType { .field public int32 item .field public 'A' * next }
Hashing ensures that two definitions of the structure in different modules will always evaluate to the same name. See the function ".class public sequential sealed serializable ansi 'struct (4AvbtDhe7KLdkQCXpqa4ME)' extends System.ValueType { .field public int32 'x' }
CreateNewAnonName
" in "c_types.c
"
within the Portable.NET source code for precise details of the hash
algorithm to use.Complex structures need some additional fields to assist with runtime layout. For example, consider the following type:
struct B { char data[sizeof(void *)]; struct B *next; };
To assist with computing the size of this structure and with accessing its
fields, we add static fields called "size.of
" and
"next.offset
":
.class public sequential serializable beforefieldinit sealed ansi 'A' extends System.ValueType { .field public 'array char[sizeof(void *)]' data .field public 'B' * next .field public static initonly unsigned int32 'size.of' .field public static initonly unsigned int32 'next.offset' .method private static hidebysig specialname rtspecialname void .cctor() cil managed { ... } }
The static constructor computes the size of the structure, and the offset
of the "next
" field, and places them into the respective
static fields. There is no need to compute the offset of
"data
" because it will always be zero. In C#, the
static constructor code would look something like this:
'next.offset' = Crt0.Align(sizeof(byte) * sizeof(void *), 0x0400); 'size.of' = Crt0.Align('next.offset' + sizeof(B *), 0x0401);
The "next
" field is aligned using pointer alignment,
as it is a pointer. The entire structure is aligned with both
pointer and byte alignment, as it contains both pointer and byte values.
The compiler may use any static constructor code that is equivalent to the above; there are plenty of other ways that those values could be computed.
Because the "size.of
" and "next.offset
" fields
are init-only, an optimizing CLR may be able to inline their constant
values once the static initializer has been executed.
union A
") are represented as value types with
the name "A
", and all fields explicitly laid out to
start at offset 0.
If the union type is complex, then it must contain a public static field called "union A { int x; double y; } .class public explicit sealed ansi 'A' extends System.ValueType { .field [0] public int32 x .field [0] public float64 y }
size.of
" which is initialized to the
type size. See the previous section on struct representation for
more information on computing size values.
Note: In this ABI, it is impossible to have both a struct and a
union with the same name, as both "struct A
" and
"union A
" will be represented by a value type called
"A
". It was considered more important that C types have
natural names, to ease integration with C# code. Since C programmers
rarely use the same name for different type kinds, this isn't expected
to be a problem in practice.
struct A { int x : 8; int y : 1; unsigned int z : 16; int w; } .class public sequential sealed ansi 'A' extends System.ValueType { .custom [OpenSystem.C.BitField("x", ".bitfield-1", 0, 8)] .custom [OpenSystem.C.BitField("y", ".bitfield-1", 8, 1)] .custom [OpenSystem.C.BitField("z", ".bitfield-2", 0, 16)] .field public int32 '.bitfield-1' .field public unsigned int32 '.bitfield-2' .field public int32 w }
System.Enum
"
and contain literal static fields for each constant value.
The C type called "enum A
" is named "A
"
within the final CLI output.
Anonymous enumerations are not encoded as CLI types. Instead, every
instance of the anonymous enumeration is replaced with the underlying
type (usually int32
). This is necessary because there
is no way to reliably give an anonymous enumeration the same name
in every module that uses it.
enum Color { Red, Green, Blue } x; enum { R, G, B } y; .field public static valuetype 'enum_Color' 'x' .field public static int32 'y' .class public auto sealed serializable ansi 'Color' extends System.Enum { .field public specialname rtspecialname int32 'value__' .field public static literal valuetype 'Color' 'Red' = int32(0x00000000) .field public static literal valuetype 'Color' 'Green' = int32(0x00000001) .field public static literal valuetype 'Color' 'Blue' = int32(0x00000002) }
A[]
" are mapped to a value
type called "array A[]
". For example, "int[]
"
is encoded as follows:
The value type must have a field called ".class public sequential sealed ansi 'array_int[]' extends System.ValueType { .field private static specialname int32 elem__ }
elem__
", which
defines the element type, and it must have the attributes
"private static specialname
".
Note. It will be rare to find a type of the form "A[]
" in a generated object file, because such types normally decay to pointer types when used as function arguments. The encoding is specified here because the compiler does need to distinguish "A[]
" from "A *
" in certain circumstances.
If the array type includes a non-zero size value, the size
is a compile-time constant, and the element type is fixed, then
the array type is defined with an explicit size. For example,
"int[100]
" is encoded as follows:
.class public explicit sealed serializable ansi 'array int[100]' extends System.ValueType { .size 400 // == 4 * 100 .field public specialname int32 elem__ }
The "elem__
" field in this case must be
"public specialname
", to distinguish it from the
open array case.
All other arrays are complex. The element type is encoded in a
field called "elem__
", and the total size is computed
at runtime and placed in the public static field "size.of
".
For example, "char[sizeof(void *)]
" and
"void *[100]
" would be encoded as follows:
.class public seqential sealed serializable ansi 'array char[sizeof(void *)]' extends System.ValueType { .field public specialname int8 elem__ .field public static unsigned int32 'size.of' .method private static hidebysig specialname rtspecialname void .cctor() cil managed { ... } } .class public seqential sealed serializable ansi 'array void *[100]' extends System.ValueType { .field public specialname void * elem__ .field public static unsigned int32 'size.of' .method private static hidebysig specialname rtspecialname void .cctor() cil managed { ... } }
OpenSystem.C.IsFunctionPointer
" modifier:
void (*func)(int); .field public static method void * (int32) modopt(IsFunctionPointer) func
An array argument to a function will be converted into its "decayed" pointer form. For example:
int main(int argc, char *argv[]) { ... } .method public static int32 main (int32 argc, int8 * * argv) cil managed { ... }
Fixed and dynamic struct and union types are passed by value.
Complex struct and union types are passed by pointer, and should be
marked with the "OpenSystem.C.IsComplexPointer
" modifier.
Complex struct and union types are returned by pointer. The function
should copy the value into a block allocated by "AllocHGlobal
"
and return a pointer to the block. The caller then copies the value
out and frees the block with "FreeHGlobal
".
Functions that take a variable number of arguments must be declared
with "vararg
" calling conventions:
int printf(const char *format, ...) { ... } .method public static vararg int32 printf (int8 modopt(IsConst) * format) cil managed { ... }
When arguments are passed to a variable-argument function, they must be converted into their "natural passing type" first:
Type | Natural Passing Type |
_Bool |
_Bool |
char |
int |
unsigned char |
int |
short |
int |
unsigned short |
int |
__wchar__ |
int |
int |
int |
unsigned int |
int |
long |
System.IntPtr |
unsigned long |
System.IntPtr |
long long |
long long |
unsigned long long |
long long |
float |
double |
double |
double |
long double |
double |
type * |
System.IntPtr |
enum |
int |
struct and union |
Same as input type |
Natural passing types help to properly implement cases where a value is passed as unsigned, but unpacked as signed, or is passed using a smaller type than the unpacking type.
The compiler must convert all variable arguments to their natural passing
types at the point of the call. The "va_arg
" operator is then
responsible for casting the natural passing type back to the programmer's
requested type.
The "va_list
" type is implemented by the C#
"System.ArgIterator
" class, and has "dynamic" layout.
The runtime engine will throw an exception if an attempt is made to
unpack an argument using the wrong natural passing type.
<Module>
"
type. However, there are some "undefined" issues that we now
deal with.
<Module>
"
type in all assemblies to store global definitions. However,
Microsoft's CLR does not allow references to the "<Module>
"
type within a foreign assembly. This appears to be a hard-wired constraint.
Other CLR's (e.g. Portable.NET) make no distinction between the module type
and all other types.
In addition, we want C# compilers to be able to link against a C library
assembly and import the definitions into a C# program. There is no way
to refer to an obscure type name like "<Module>
"
from within C# code (Portable.NET's C# compiler did have a
"__module
" keyword for this purpose at one point,
but it is deprecated).
The C linker must choose an ordinary C# identifier to name the global
module within a library assembly. We recommend using the assembly
name, minus any model qualifiers. For example, all global C definitions
in "libc.dll
" will be found in a type called
"libc
", with no namespace.
Global module types of this form must be marked with the
"OpenSystem.C.ModuleScope
" attribute and have the
"public
" and "sealed
" flags.
Executables still use the "<Module>
" type, as it appears
to work in all CLR's that have been tested so far. The
"<Module>
" type should have the "public
"
and "abstract
" flags.
In C# code, the attribute "OpenSystem.C.GlobalScope
" can
be used to mark a class whose definitions are visible as part of the
global C scope. This allows C# libraries to export functionality to
C programs in a form that looks like a regular C function. The linker
is responsible for noticing this attribute and performing the necessary
fixups.
Note: "GlobalScope
and "ModuleScope
" are
similar in that they both export definitions into the global C scope.
"ModuleScope
" also marks the type as the main module type,
which is expected to have special methods called ".init
"
and ".fini
". Marking a regular C# class with
"ModuleScope
" is likely to confuse the linker.
When the assembler sees a dangling reference to something in the
"<Module>
" class, it will convert it into a member
reference on the "<ModuleExtern>
" class. For example:
If.method public static void hello() cil managed { call void hello2() }
hello2
remains undefined at the end of the assembly
process, then the resulting object file will look like this:
When the linker loads this object file, it will resolve references to ".method public static void hello() cil managed { call void '<ModuleExtern>'::hello2() }
<ModuleExtern>
" by looking for a matching definition
and changing the type reference appropriately. The new reference
may be to the linked executable's "<Module>
" type,
or to a foreign library's global module type.
The "<ModuleExtern>
" type will itself be dangling.
The exact means by which this is accomplished is compiler-dependent, as the
ECMA specification does not define an object file format for the CLI.
Portable.NET's assembler encodes dangling types as a
TypeRef, scoped to the current module, but with no corresponding TypeDef.
The object file format is based on the native PE/COFF object file format,
with CIL metadata stored in the ".text$il
" section.
Portable.NET's linker fixes up dangling TypeRef's at link time.
static
" are converted
into "private
" fields or methods within the
"<Module>
" object file's class. All other variables
or functions are converted into "public
" definitions.
If the "<Module>
" class has any "public
"
members, then the class will also be declared "public
".
This ensures that a library will export its definitions correctly to
applications that link against the library.
private
" definition for the same function
or variable. Alternatively, one may be "private
" and
the other "public
".
We resolve this situation by renaming one of the "private
"
definitions to something else, and then redirecting all references to
the original to the renamed version. From an external user's point of
view, the "public
" definition (if any) will become the
visible definition. For example:
File 1:If two or more object files have conflicting ".field public static int32 xFile 2:.field private static float64 x .method public static float64 getx() cil managed { ldsfld float64 x ret }Result:.field public static int32 x .field private static float64 'x-1' .method public static float64 getx() cil managed { ldsfld float64 'x-1' ret }
public
"
definitions for a function or variable, then a linker error will occur.
Structure, union, and array types may also conflict when two
object files are linked together. In most cases, the two definitions
will be the same, because the same type is being used in both object
files (e.g. "struct __stdio_file
" in Portable.NET's
stdio implementation).
When two types have identical definitions, the linker will copy one into the output file and ignore the other. When the two types have different definitions, the linker chooses one to become the primary copy, and the other is renamed.
If one of the types has the same definition as a type from a library, the linker should favour the library's definition, as it is the most likely candidate. If neither definition duplicates a library definition, the linker can choose either one, and probably should also report a warning to the programmer.
When program items are renamed, the resultant binary will not be in
sync with the source code. This can make source-level debugging
difficult. To alleviate this problem, the linker can add
"OriginalName
" attribute values to all renamed items:
Normally this is only required if an object file contained debug symbol information prior to renaming..field public static int32 x .field private static float64 'x-1' .custom [OpenSystem.C.OriginalName("x")] .method public static float64 getx() cil managed { ldsfld float64 'x-1' ret }
getuid
" function (paraphrased a little):
This will be compiled as follows:int __getuid(void) { ... } weak_alias(__getuid, getuid)
When a program is linked against this definition, the ".method public static int32 __getuid() cil managed { ... } .field public specialname static .method int32 * () 'getuid-alias' .method public static int32 getuid() cil managed { .custom [OpenSystem.C.WeakAliasFor("__getuid")] .maxstack 1 ldsfld .method int32 * () 'getuid-alias' tail. calli int32 () ret } .method private specialname static void '.init-1'() cil managed { .custom [OpenSystem.C.Initializer] .maxstack 1 ldftn void __getuid() stsfld .method int32 * () 'getuid-alias' ret }
WeakAliasFor
" attribute is used to redirect the
reference to the actual definition if the system does not contain
any other definitions for the function.
When a library that does not supply its own "getuid
"
is linked against this definition, the "getuid
"
method is called directly, which will then redirect control to
the actual "getuid
".
A program or library that defines its own "getuid
"
is compiled as normal:
At link time, the linker will insert an initializer which updates the ".method public static int32 getuid() cil managed { ... }
getuid-alias
" field with the new value:
where ".method private specialname static void '.init-1'() cil managed { .custom [OpenSystem.C.Initializer] .maxstack 1 ldftn void getuid() stsfld .method int32 * () [library]'$Module$'::'getuid-alias' ret }
library
" is the name of the library that defines
the "getuid-alias
" variable.Strong aliases for functions are defined in a similar manner:
In this case, whenever the linker sees a reference to ".method public static vararg int32 _IO_printf (int8 modopt(OpenSystem.C.IsConst) *format) cil managed { ... } .method public static vararg int32 printf (int8 modopt(OpenSystem.C.IsConst) *format) cil managed { .custom [OpenSystem.C.StrongAliasFor("_IO_printf")] }
printf
",
it will redirect the caller to "_IO_printf
". The body of
the alias function is empty, because it will never be called at runtime.Global variables may also have strong aliases associated with them:
When the linker sees a reference to "char **__environ; strong_alias(__environ, environ); .field public static int8 * * __environ .field public static int8 * * environ .custom [OpenSystem.C.StrongAliasFor("__environ")]
environ
", it will
substitute "__environ
".
Weak aliases are not supported for global variables. Weak aliases
exist in libc libraries primarily for legacy reasons. There are
existing C programs that depend upon variables like "environ
",
"timezone
", etc, being weak aliases, but they are rarer
than programs that depend upon functions being weak aliases.
It is recommended that if the compiler sees a weak alias definition for a variable that it output a strong alias instead.
specialname
" flag, have no parameters or return
values, and are marked with the "Initializer
"
attribute.
Finalizers are compiled into static methods that have the
"specialname
" flag, have no parameters or return
values, and are marked with the "Finalizer
" attribute.
The linker collects up all initializers and finalizers in a program or library and does the following:
public
methods in the
"<Module>
" class: ".init
"
and ".fini
"..init
" method calls the ".init
"
methods of all libraries that the program or library itself
depends upon..init
" method then calls all of the
locally-defined initializers..fini
" method calls all of the locally-defined
finalizers..fini
" method then calls the ".fini
"
methods of all libraries that the program or library itself
depends upon, in reverse order..init
" methods are called is usually
indeterminable. The compiler can alter the ordering using the
"InitializerOrder
" attribute:
This initializer will be executed before all "normal" initializers, which have a default order value of zero..method private specialname static void '.init-1'() cil managed { .custom [OpenSystem.C.Initializer] .custom [OpenSystem.C.InitializerOrder(-1)] ... }
The "FinalizerOrder
" attribute can used to alter the
ordering of finalizers. A finalizer with an order value of -1 will
be executed after the normal finalizers.
When the linker generates the ".init
" and ".fini
"
methods, it must also insert some reference counting code. The body
of the ".init
" method will only be executed upon the first
call, and the body of the ".fini
" method will only be
executed upon the last call. Appendix A contains some sample code
that demonstrates this.
Usually, locally-defined initializers and finalizers are declared
"private
". The renaming logic described in a previous
section will take care of resolving ambiguities in naming.
main
" function is compiled,
a small amount of CIL code is added to define the application entry point.
This code calls facilities in the "OpenSystem.C.Crt0
" class
to initialize the application, to invoke "main
", and to
handle shutdown tasks when "main
" exits. Using C# syntax,
the startup code looks something like this:
public static void .start(String[] args) { try { int argc; IntPtr argv; IntPtr envp; argv = Crt0.GetArgV(args, out argc); envp = Crt0.GetEnvironment(); Crt0.Startup(); Crt0.Shutdown(main(argc, argv, envp)); } catch(OutOfMemoryException) { throw; } catch(Object e) { throw Crt0.ShutdownWithException(e); } }The compiler will only pass those parameters to "
main
"
that the programmer specified in their source code.
The startup code in the application is kept deliberately simple,
with most of the real work being done in the "OpenSystem.C.Crt0
"
class. This allows the crt0 code to be modified to accomodate new libc
requirements in the future, without needing all existing applications
to be recompiled.
.class private sealed '.init-count' extends System.Object { .field private static int32 count } .method public specialname static void '.init'() cil managed { .maxstack 2 .locals (class System.Type) // Lock down '.init-count' to synchronize access. ldtoken '.init-count' call class System.Type System.Type::GetTypeFromHandle (valuetype System.RuntimeTypeHandle) dup stloc 0 call void System.Threading.Monitor::Enter(class System.Object) .try { // Increase the reference count, and check for the first call. ldsfld int32 '.init-count'::count dup ldc.i4.1 add stsfld int32 '.init-count'::count brtrue L1 leave runinit L1: leave exit } finally { ldloc 0 call void System.Threading.Monitor::Exit(class System.Object) endfinally } runinit: // Run the initializers for the libraries. call void [libc64]'libc'::'.init'() // Run the local initializers. call void '<Module>'::'.init-1'() call void '<Module>'::'.init-2'() ... call void '<Module>'::'.init-N'() exit: // Initialization has finished. ret } .method public specialname static void '.fini'() cil managed { .maxstack 2 .locals (class System.Type) // Lock down '.init-count' to synchronize access. ldtoken '.init-count' call class System.Type System.Type::GetTypeFromHandle (valuetype System.RuntimeTypeHandle) dup stloc 0 call void System.Threading.Monitor::Enter(class System.Object) .try { // Decrease the reference count, and check for the last call. ldsfld int32 '.init-count'::count ldc.i4.1 sub dup stsfld int32 '.init-count'::count brtrue L1 leave runfini L1: leave exit } finally { ldloc 0 call void System.Threading.Monitor::Exit(class System.Object) endfinally } runfini: // Run the local finalizers. call void '<Module>'::'.fini-1'() call void '<Module>'::'.fini-2'() ... call void '<Module>'::'.fini-N'() // Run the finalizers for the libraries. call void [libc64]'libc'::'.fini'() exit: // Finalization has finished. ret }