Click here to monitor SSC

Simon Cooper

Subterranean IL: Pseudo custom attributes

Published Tuesday, November 30, 2010 5:25 PM

Custom attributes were designed to make the .NET framework extensible; if a .NET language needs to store additional metadata on an item that isn't expressible in IL, then an attribute could be applied to the IL item to represent this metadata. For instance, the C# compiler uses DecimalConstantAttribute and DateTimeConstantAttribute to represent compile-time decimal or datetime constants, which aren't allowed in pure IL, and FixedBufferAttribute to represent fixed struct fields.

How attributes are compiled

Within a .NET assembly are a series of tables containing all the metadata for items within the assembly; for instance, the TypeDef table stores metadata on all the types in the assembly, and MethodDef does the same for all the methods and constructors. Custom attribute information is stored in the CustomAttribute table, which has references to the IL item the attribute is applied to, the constructor used (which implies the type of attribute applied), and a binary blob representing the arguments and name/value pairs used in the attribute application.

For example, the following C# class:

[Obsolete("Please use MyClass2", true)]
public class MyClass {
    // ...
}
corresponds to the following IL class definition:
.class public MyClass {
    .custom instance void
        [mscorlib]System.ObsoleteAttribute::.ctor(string, bool)
        = { string('Please use MyClass2' bool(true) }
    // ...
}
and results in the following entry in the CustomAttribute table:
TypeDef(MyClass)
MemberRef(ObsoleteAttribute::.ctor(string, bool))
blob -> {string('Please use MyClass2' bool(true)}
However, there are some attributes that don't compile in this way.

Pseudo custom attributes

Just like there are some concepts in a language that can't be represented in IL, there are some concepts in IL that can't be represented in a language. This is where pseudo custom attributes come into play.

The most obvious of these is SerializableAttribute. Although it looks like an attribute, it doesn't compile to a CustomAttribute table entry; it instead sets the serializable bit directly within the TypeDef entry for the type. This flag is fully expressible within IL; this C#:

[Serializable]
public class MySerializableClass {}
compiles to this IL:
.class public serializable MySerializableClass {}

For those interested, a full list of pseudo custom attributes is available here. For the rest of this post, I'll be concentrating on the ones that deal with P/Invoke.

P/Invoke attributes

P/Invoke is built right into the CLR at quite a deep level; there are 2 metadata tables within an assembly dedicated solely to p/invoke interop, and many more that affect it. Furthermore, all the attributes used to specify p/invoke methods in C# or VB have their own keywords and syntax within IL. For example, the following C# method declaration:

[DllImport("mscorsn.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.U1)]
private static extern bool StrongNameSignatureVerificationEx(
    [MarshalAs(UnmanagedType.LPWStr)] string wszFilePath,
    [MarshalAs(UnmanagedType.U1)] bool fForceVerification,
    [MarshalAs(UnmanagedType.U1)] ref bool pfWasVerified);
compiles to the following IL definition:
.method private static pinvokeimpl("mscorsn.dll" lasterr winapi)
bool marshal(unsigned int8)
StrongNameSignatureVerificationEx(
    string marshal(lpwstr) wszFilePath,
    bool marshal(unsigned int8) fForceVerification,
    bool& marshal(unsigned int8) pfWasVerified) cil managed preservesig {}
As you can see, all the p/invoke and marshal properties are specified directly in IL, rather than using attributes. And, rather than creating entries in CustomAttribute, a whole bunch of metadata is emitted to represent this information. This single method declaration results in the following metadata being output to the assembly:
  • A MethodDef entry containing basic information on the method
  • Four ParamDef entries for the 3 method parameters and return type
  • An entry in ModuleRef to mscorsn.dll
  • An entry in ImplMap linking ModuleRef and MethodDef, along with the name of the function to import and the pinvoke options (lasterr winapi)
  • Four FieldMarshal entries containing the marshal information for each parameter.
Phew!

Applying attributes

Most of the time, when you apply an attribute to an element, an entry in the CustomAttribute table will be created to represent that application. However, some attributes represent concepts in IL that aren't expressible in the language you're coding in, and can instead result in a single bit change (SerializableAttribute and NonSerializedAttribute), or many extra metadata table entries (the p/invoke attributes) being emitted to the output assembly.

by Simon Cooper
Filed Under:

Comments

No Comments
You need to sign in to comment on this blog

About Simon Cooper

Simon joined Red Gate as a software developer in 2007. He started off in the SQL Tools division, working on SQL Compare 7 and 8, moved onto Schema Compare for Oracle, and is now in the .NET division working on SmartAssembly.
Latest articles
A first look at SQL Server 2012 Availability Group Wait Statistics
 If you are trouble-shooting an AlwaysOn Availability Group topology, a study of the wait statistics... Read more...

SQL Server Prefetch and Query Performance
 Prefetching can make a surprising difference to SQL Server query execution times where there is a high... Read more...

SSIS Basics: Setting Up Your Initial Package
 When working with databases, the use of SQL Server Integration Services (SSIS) is a skill that often... Read more...

Checking Out SQL Backup Pro 7’s New Automatic Backup Verification
 Wouldn't it be great to offload the daily chore of checking the integrity of your production... Read more...

Chuck Lathrope: DBA of the Day
 Chuck Lathrope was a finalist for the Exceptional DBA of the Year award in 2009. We contacted him to... Read more...