Application Binary Interface for C within CLI

Rhys Weatherley, rweather@southern-storm.com.au.
Last Modified: $Date: 2004/03/06 09:47:38 $

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.

1. Introduction

This document describes an Application Binary Interface (ABI) for the C language within Common Language Infrastructure (CLI) environments that meets the following goals:

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.
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.
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.

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.

2. ABI support library

The "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
An attribute that marks an assembly as being a C module.
IsConst
A modifier class for marking a type as "const".
IsFunctionPointer
A modifier class for marking a type as a function pointer, rather than as a function signature.
IsComplexPointer
A modifier class for marking a type that is passed as a pointer to a function but is really a complex pass by value struct or union type.
BitFieldAttribute
An attribute that describes the position and size of a bit field within a larger integer field.
WeakAliasForAttribute
An attribute that marks a method as defining a weak alias.
StrongAliasForAttribute
An attribute that marks a field or method as defining a strong alias.
InitializerAttribute
An attribute that marks special methods that provide static initialization logic to be executed at program startup. These methods are different from CLI static constructors (.cctor methods), in that initializers are guaranteed to be executed before main.
InitializerOrderAttribute
An attribute that defines the ordering of an initializer relative to all others. Initializers with a lower order value are called before those with higher order values. The default order value is zero.
FinalizerAttribute
An attribute that marks special methods that provide static finalization logic to be executed at program shutdown. These methods are not the same as the "Finalize" methods in garbage-collected objects. The order of garbage-collected object finalization is indeterminate with respect to C finalizers.
FinalizerOrderAttribute
An attribute that defines the ordering of a finalizer relative to all others. Finalizers with a lower order value are called after those with higher order values. The default order value is zero. Normally an initializer and its corresponding finalizer will have identical order values.
OriginalNameAttribute
An attribute that specifies the original name of a symbol that had to be renamed to resolve link-time naming conflicts.
LongJmpException
An exception class that assists the ABI in implementing setjmp/longjmp operations.
Crt0
A class that provides utility methods to manage the startup and shutdown of C applications.
FloatComplex, DoubleComplex
Value types that correspond to the ISO C complex number types.
FloatImaginary, DoubleImaginary
Value types that correspond to the ISO C imaginary number types.
CNameAttribute
An attribute that specifies the C name of a value type defined in C# code. e.g. "FloatComplex" is marked as "float _Complex".
GlobalScopeAttribute
Used in C# code to mark a class whose definitions should be treated as part of the global C scope during linking.
ModuleScopeAttribute
Used by linkers to mark the type within a library assembly that is standing in for the global "<Module>" type.
The meaning of these classes will become clearer in later sections. Other classes may be provided to support "libc" implementations, but they are beyond the scope of this specification.

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:

.module test.exe
.custom instance void [OpenSystem.C]OpenSystem.C.ModuleAttribute::.ctor()
       = (01 00 00 00)

.module test.exe
.custom [OpenSystem.C.Module]
This abbreviation is for exposition purposes only. It isn't intended to suggest an alternative syntax for CIL assemblers.

3. Marking C modules for the linker

Every assembly or object file that conforms to this ABI must be marked with the "OpenSystem.C.ModuleAttribute":

.module test.exe
.custom instance void [OpenSystem.C]OpenSystem.C.ModuleAttribute::.ctor()
       = (01 00 00 00)
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.

4. Type representation

4.1. Primitive types

The names and sizes of the primitive types in this ABI are defined as follows:

TypeSizeDescription
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".

4.2. Type qualifiers

The "const" and "volatile" qualifiers are represented using the "OpenSystem.C.IsConst" and "System.Runtime.CompilerServices.IsVolatile" modifiers. The following table provides some examples:

DeclarationRepresentation
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).

4.3. Fixed, dynamic, complex, and unknown types

Types in the ABI may have four categories of layout: "fixed", "dynamic", "complex", or "unknown".

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.]

4.3.1. Determining the type category

The following primitive C types are always fixed: "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];

4.3.2. Getting the size of a type

The size of a fixed type can be evaluated to a compile time constant.

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).

4.3.3. Getting alignment information for a type

When computing the size of complex types, it is necessary to explicitly align size values on the appropriate boundaries. The compiler cannot determine what the boundary may be at compile time, but it can use a metadata coding trick to intuit the boundary at runtime.

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
The type contains "byte" fields.
0x0002
The type contains fields with explicit 2-byte alignment.
0x0004
The type contains fields with explicit 4-byte alignment.
0x0008
The type contains fields with explicit 8-byte alignment.
0x0010
The type contains fields with explicit 16-byte alignment.
0x0020
The type contains "short" fields.
0x0040
The type contains "int" fields.
0x0080
The type contains "long long" fields.
0x0100
The type contains "float" fields.
0x0200
The type contains "double" fields.
0x0400
The type contains pointer or "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.

4.4. Struct representation

Structure types (e.g. "struct A") are converted into a value type called "A", with no namespace qualifier. This value type is marked as having sequential layout:
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
}
Anonymous structures are assigned a unique value based on hashing the fields within the structure using the MD5 hash algorithm:

.class public sequential sealed serializable ansi
          'struct (4AvbtDhe7KLdkQCXpqa4ME)' extends System.ValueType
{
    .field public int32 'x'
}
Hashing ensures that two definitions of the structure in different modules will always evaluate to the same name. See the function "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.

4.5. Union representation

Unions (e.g. "union A") are represented as value types with the name "A", and all fields explicitly laid out to start at offset 0.
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
}
If the union type is complex, then it must contain a public static field called "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.

4.6. Representation of bit fields

Bit fields are represented as regular fields, with an attribute to indicate the field's position and size. Bits are always allocated from the least significant bit, as there is no way to know what bit order is used by the runtime platform.

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
}

4.7. Enumerated types

Enumerated types are encoded using the same CLI mechanisms as C#. That is, enumerated types inherit from "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)
}

4.8. Array types

Array types of the form "A[]" are mapped to a value type called "array A[]". For example, "int[]" is encoded as follows:
.class public sequential sealed ansi 'array_int[]'
            extends System.ValueType
{
    .field private static specialname int32 elem__
}
The value type must have a field called "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
    {
        ...
    }				
}

4.9. Function pointer types

CLI metadata uses the same representation for method signatures and pointers to methods. C requires that signatures and pointers be distinct type categories. We therefore mark function pointers with the "OpenSystem.C.IsFunctionPointer" modifier:
void (*func)(int);

.field public static method void * (int32) modopt(IsFunctionPointer) func

4.10. Argument types

When arguments are passed to a function, it is sometimes necessary to alter the type to conform with C conventions or to work around overly-strict CLI requirements.

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:

TypeNatural 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.

5. Defining global fields and methods

The Common Language Infrastructure (CLI) has support for global fields and methods in the specially-defined "<Module>" type. However, there are some "undefined" issues that we now deal with.

5.1. Interoperability considerations

Ideally, we would like to use the global "<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.

5.2. Dangling references

When a C source file is compiled to an object file, there will normally be "dangling" references to fields and methods in other object files and libraries. We need to handle this in the assembler and 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:

.method public static void hello() cil managed
{
    call void hello2()
}
If hello2 remains undefined at the end of the assembly process, then the resulting object file will look like this:

.method public static void hello() cil managed 
{
    call void '<ModuleExtern>'::hello2()
}
When the linker loads this object file, it will resolve references to "<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.

5.3. Access permissions

Variables or functions that are declared "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.

5.4. Renaming conflicting definitions

When two object files are linked together, it is possible that they both may have a "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:
.field public static int32 x
File 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
}
If two or more object files have conflicting "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:

.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
}
Normally this is only required if an object file contained debug symbol information prior to renaming.

5.5. Weak and strong aliases

Modern C libraries make heavy use of weak aliases to allow programs to replace certain functions with their own implementation. For example, the following is used in "glibc" for the definition of the "getuid" function (paraphrased a little):
int __getuid(void)
{
    ...
}

weak_alias(__getuid, getuid)
This will be compiled as follows:
.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
}
When a program is linked against this definition, the "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:

.method public static int32 getuid() cil managed
{
    ...
}
At link time, the linker will insert an initializer which updates the "getuid-alias" field with the new value:
.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
}
where "library" is the name of the library that defines the "getuid-alias" variable.

Strong aliases for functions are defined in a similar manner:

.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")]
}
In this case, whenever the linker sees a reference to "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:

char **__environ;
strong_alias(__environ, environ);

.field public static int8 * * __environ

.field public static int8 * * environ
.custom [OpenSystem.C.StrongAliasFor("__environ")]
When the linker sees a reference to "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.

5.6. Initializers and finalizers

Initializers are compiled into static methods that have the "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:

  1. It creates two "public methods in the "<Module>" class: ".init" and ".fini".
  2. The ".init" method calls the ".init" methods of all libraries that the program or library itself depends upon.
  3. The ".init" method then calls all of the locally-defined initializers.
  4. The ".fini" method calls all of the locally-defined finalizers.
  5. The ".fini" method then calls the ".fini" methods of all libraries that the program or library itself depends upon, in reverse order.
The order in which ".init" methods are called is usually indeterminable. The compiler can alter the ordering using the "InitializerOrder" attribute:

.method private specialname static void '.init-1'() cil managed
{
    .custom [OpenSystem.C.Initializer]
    .custom [OpenSystem.C.InitializerOrder(-1)]
    ...
}
This initializer will be executed before all "normal" initializers, which have a default order value of zero.

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.

6. The crt0 code

When a module containing a "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.

Appendix A. Sample initialization and finalization code

.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
}