I've been meaning to write the page for a while, but haven't found time. So, at last, here's a very quick summary of how ActiveSharp works.

When you set a property value

  1. Property setter method calls the SetValue method which you defined (typically on your base class). By the way, it doesn't have to be called SetValue, you can call it anything.
  2. SetValue passes "this" and the field being set (by ref) to ActiveSharp.PropertyMapping.PropertyMap.GetProperty.
  3. GetProperty calls a special routine which computes the difference between the memory locations of the "this" pointer and the field, like so:
    1. Briefly pin the object in memory, so the garbage collector won't move it while we are working
    2. Cast the "this" pointer to an untyped pointer (i.e. a raw memory address)
    3. Cast the field's location to an untyped pointer (again, a raw memory address). We can do this only because we recieved the field "by ref"
    4. Subtract one raw memory address from the other, to give an "offset value" - i.e. the field's unique position within it's containing object
    5. Unpin the object (total duration of the pinning is very short)
  4. Use the offset value to look up the field name. We do this lookup in a hashtable that we prepared earlier (see below).
  5. Use the field name to look up the property. Again, we do this in a hashtable that we prepared earlier (see below)
  6. Now we have the property. DONE

(Note: the two hashtable lookups at the end may be optimized away in a future code change)

One-off setup

ActiveSharp automatically initializes itself at runtime. There are three things to initialize:

1. The offset calculation routine

If you were thinking that its difficult to cast things to raw memory addresses in C#, you'd be right. In fact, it's impossible. My first version of this code used C++ / CLI instead, but even that couldn't cast a "this" pointer (it could only do fields). The older "Managed Extensions" for C++ can cast the this pointer.

However, to save messing around with assemblies written in other languages, what ActiveSharp actually does is it generates the necessary routine at runtime, by creating the raw MSIL with Reflection.Emit. That lets us use a pure C# solution to do something that actually can't be done in C# ;-)

We make this routine once, the first time that ActiveSharp is used, and then we hold onto it for all future use.

2. The hash table that maps offsets to fields

We need one hash table for each class in which ActiveSharp is used (i.e. each different class which SetValue passes as the "this" pointer). ActiveSharp intializes the hash table lazily, building the table for a given type the first time it is needed. We build it by passing every field in the object to our offset finder method and seeing what offset number it returns. The returned number becomes the key in the hash table and the field name becomes the value.

How do we pass every field, especially of they are private fields? We use Lightweight Code Gen since, unlike standard Reflection Emil, LGC can access private fields (given appropriate reflection permissions). I.e. we use LGC to make a method that passes each field to the offset finder routine. We only need to call the method once. After that, we just use its hash-tabled results.

3. The hash table that maps fields to properties

For this, we inspect the MSIL of the property setter methods, to see which field is set by each one. We can do this because, as of .NET 2.0, we can get method bodies at runtime in MSIL form. We scan the MSIL instructions, with a very basic parsing algorithm, to find out what we need to know.
We do this once, and cache the result.

Any questions - leave a comment or email me

Last edited Dec 30, 2010 at 3:35 AM by johnrusk, version 17

Comments

No comments yet.