I have the following class:
[StructLayout(LayoutKind.Sequential)]
class Class
{
public int Field1;
public byte Field2;
public short? Field3;
public bool Field4;
}
How can I get the byte offset of Field4
starting from the start of the class data (or object header)?
To illustrate:
Class cls = new Class();
fixed(int* ptr1 = &cls.Field1) //first field
fixed(bool* ptr2 = &cls.Field4) //requested field
{
Console.WriteLine((byte*)ptr2-(byte*)ptr1);
}
The resulting offset is, in this case, 5, because the runtime actually moves Field3
to the end of the type (and pads it), probably because it its type is generic. I know there is Marshal.OffsetOf
, but it returns unmanaged offset, not managed.
How can I retrieve this offset from a FieldInfo
instance? Is there any .NET method used for that, or do I have to write my own, taking all the exceptions into account (type size, padding, explicit offsets, etc.)?
Offset of a field within a class or struct in .NET 4.7.2:
These return the byte offset of a field within a
class
orstruct
, relative to the layout of some respective managed instance at runtime. This works for allStructLayout
modes, and for both value- and reference-types (including generics, reference-containing, or otherwise non-blittable). The offset value is zero-based relative to the beginning of the user-defined content or 'data body' of thestruct
orclass
only, and doesn't include any header, prefix, or other pad bytes.Discussion
Since
struct
types have no header, the returned integer offset value can used directly via pointer arithmetic, and System.Runtime.CompilerServices.Unsafe if necessary (not shown here). Reference-type objects, on the other hand, have a header which has to be skipped-over in order to reference the desired field. This object header is usually a singleIntPtr
, which meansIntPtr.Size
needs to be added to the the offset value. It is also necessary to dereference the GC ("garbage collection") handle to obtain the object's address in the first place.With these considerations, we can synthesize a tracking reference to the interior of a GC object at runtime by combining the field offset (obtained via the method shown above) with an instance of the
class
(e.g. anObject
handle).The following method, which is only meaningful for
class
(and notstruct
) types, demonstrates the technique. For simplicity, it uses ref-return and the System.Runtime.CompilerServices.Unsafe libary. Error checking, such as assertingfi.DeclaringType.IsSubclassOf(obj.GetType())
for example, is also elided for simplicity.This method returns a managed "tracking" pointer into the interior of the garbage-collected object instance
obj
. It can be used to arbitrarily read or write the field, so this one function replaces the traditional pair of separate getter/setter functions. Although the returned pointer cannot be stored in the GC heap and thus has a lifetime limited to the scope of the current stack frame (i.e., and below), it is very cheap to obtain at any time by simply calling the function again.Note that this generic method is only parameterized with
<U>
, the type of the fetched pointed-at value, and not for the type ("<T>
", perhaps) of the containing class (the same applies for the IL version below). It's because the bare-bones simplicity of this technique doesn't require it. We already know that the containing instance has to be a reference (class
) type, so at runtime it will present via a reference handle to a GC object withobject
header, and those facts alone are sufficient here; nothing further needs to be known about putative type "T
".In my own use, rather than passing a
FieldInfo
or its respectiveFieldHandle
every time, what I actually retain are the various integer offset values for the fields of interest as returned fromGetFieldOffset
, since these are also invariant at runtime, once obtained. This eliminates the extra step (of callingGetFieldOffset
) each time the pointer is fetched. In fact, since I am able to include IL code in my projects, here is the exact code that I use for the function above. As with the C# just shown, it trivially synthesizes a managed pointer from a containing GC-objectobj
, plus a (retained) integer offsetoffs
within it.So even if you are not able to directly incorporate this IL, showing it here, I think, nicely illustrates the extremely low runtime overhead and alluring simplicity, in general, of this technique.
Example usage
The first demonstration gets the integer offset of reference-typed field
s1
within an instance ofMyClass
, and then uses it to get and set the field value.If this seems a bit cluttered, you can dramatically clean it up by retaining the managed pointer as ref local variable. As you know, this type of pointer is automatically adjusted--with interior offset preserved--whenever the GC moves the containing object. This means that it will remain valid even as you continue accessing the field unawares. In exchange for allowing this capability, the CLR requires that the
ref
local variable itself not be allowed to escape its stack frame, which in this case is enforced by the C# compiler.Summary
The usage examples focused on using the technique with a
class
object, but as noted, theGetFieldOffset
method shown here works perfectly fine withstruct
as well. Just be sure not to use theRefFieldValue
method with value-types, since that code includes adjusting for an expected object header. For that simpler case, just useSystem.Runtime.CompilerServicesUnsafe.AddByteOffset
for your address arithmetic instead.Needless to say, this technique might seem a bit radical to some. I'll just note that it has worked flawlessly for me for many years, specifically on .NET Framework 4.7.2, and including 32- and 64-bit mode, debug vs. release, plus whichever various JIT optimization settings I've tried.
With some tricks around
TypedReference.MakeTypedReference
, it is possible to obtain the reference to the field, and to the start of the object's data, then just subtract. The method can be found in SharpUtils.