This is the twelfth part of the .NET Internals Cookbook series. For your convenience you can find other parts in the table of contents in Part 0 – Table of contents
Table of Contents
82. What is a bit-mapped attribute? Custom attribute? Pseudo-custom attribute?
There are three types of attributes:
- Bit-mapped — for instance
public
- Custom — the “normal” attributes like
[MyCustom]
- Pseudo-custom — look like custom attributes but behave like bit-mapped ones, for instance
Serializable
Let’s take this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using System; [Custom] [Serializable] public class Program { public static void Main() { } } class CustomAttribute : Attribute { public CustomAttribute() { } } |
Which you can decompile to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
.class public auto ansi serializable beforefieldinit Program extends [mscorlib]System.Object { .custom instance void CustomAttribute::.ctor() = ( 01 00 00 00 ) // Methods .method public hidebysig static void Main () cil managed { // Method begins at RVA 0x2050 // Code size 2 (0x2) .maxstack 8 .entrypoint IL_0000: nop IL_0001: ret } // end of method Program::Main .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x2053 // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: ret } // end of method Program::.ctor } // end of class Program |
So you can see that the Serializable
attribute is applied in the class signature:
1 2 |
.class public auto ansi serializable beforefieldinit Program extends [mscorlib]System.Object |
Custom attribute is initialized this way:
1 2 3 |
.custom instance void CustomAttribute::.ctor() = ( 01 00 00 00 ) |
See also this post.
83. What is a handle-recycling attack?
SafeHandle
identifies a native handle assigned by operating system. Handles are just numbers in a handle table which can be reused. Imagine a situation that you push a handle on the stack and then the thread is paused. Other thread closes the handle, opens something new and gets the same integer value. Now our thread is resumed and continues with the handle it already pushed on the stack but now the handle identifies different resource.
84. What handle types do we have in .NET?
- Strong handle – like a normal reference
- Short weak handle – doesn’t stop the object from cleaning up, does not track the object if it is resurrected
- Long weak handle – like short weak handle but tracks the object after it gets resurrected
- Pinned handle – strong handle which doesn’t allow the object to be moved
- Async pinned handle – like pinned handle but GC knows that it can be unpinned after async (typically I/O) operation is completed
- Ref count handle – handle counting references, used for COM interop
- Dependent handle – used by
ConditionalWeakTable
, connects two objects by strong handle, but from the outside is like a weak handle
You can see those by running !gchandles
in WinDBG:
1 2 3 4 5 6 7 8 9 |
0:000> !gchandles GC Handle Statistics: Strong Handles: 259 Pinned Handles: 137 Async Pinned Handles: 1 Ref Count Handles: 79 Weak Long Handles: 197 Weak Short Handles: 650 Other Handles: 0 |
85. What is a ref local?
.NET has a concept of managed pointers — pointers not necessarily pointing to the beginning of the object. They can point to local variables, parameters and parts of an object (including array elements).
ref local
is a variable storing a managed pointer. You can use it like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using System; namespace Program { public class Program { public static void Main(string[] args) { int a = 5; ref int b = ref a; Console.WriteLine(a); Console.WriteLine(b); a = 6; Console.WriteLine(a); Console.WriteLine(b); b = 7; Console.WriteLine(a); Console.WriteLine(b); } } } |
The output is:
1 2 3 4 5 6 |
5 5 6 6 7 7 |
So you can see that both of the variables point to the same memory location. When you decompile the code, you get:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.maxstack 2 .locals init ([0] int32 a, [1] int32& b) IL_0000: nop IL_0001: ldc.i4.5 IL_0002: stloc.0 IL_0003: ldloca.s a IL_0005: stloc.1 IL_0006: ldloc.0 IL_0007: call void [mscorlib]System.Console::WriteLine(int32) IL_000c: nop IL_000d: ldloc.1 IL_000e: ldind.i4 IL_000f: call void [mscorlib]System.Console::WriteLine(int32) IL_0014: nop |
So you can see that the type of b
is int32&
.
You can also use ref return
to return something from a function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using System; namespace Program { public class Program { public static void Main(string[] args) { var array = new int[] { 5 }; ref int bar = ref Foo(array); Console.WriteLine(array[0]); Console.WriteLine(bar); array[0] = 6; Console.WriteLine(array[0]); Console.WriteLine(bar); bar = 7; Console.WriteLine(array[0]); Console.WriteLine(bar); } static ref int Foo(int[] array) { return ref array[0]; } } } |
This prints:
1 2 3 4 5 6 |
5 5 6 6 7 7 |
As a sidenote, dotnetfiddle gives this:
1 2 3 4 5 6 7 |
Run-time exception (line -1): Operation could destabilize the runtime. Stack Trace: [System.Security.VerificationException: Operation could destabilize the runtime.] at Program.Program.Foo(Int32[] array) at Program.Program.Main(String[] args) |
86. What is an interior pointer?
Interior pointer is a managed pointer which points to the inside of an object. See this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using System; namespace Program { public class Program { public static void Main(string[] args) { var foo = new Foo(); foo.Bar = 5; ref int bar = ref foo.Bar; Console.WriteLine(foo.Bar); Console.WriteLine(bar); foo.Bar = 6; Console.WriteLine(foo.Bar); Console.WriteLine(bar); bar = 7; Console.WriteLine(foo.Bar); Console.WriteLine(bar); } } } class Foo{ public int Bar; } |
This outputs:
1 2 3 4 5 6 |
5 5 6 6 7 7 |
You may ask: how does GC know that interior pointer needs to keep an object alive? GC uses brick table which partitions the heap into multiple blocks. It contains an offset to the first object inside such a block so then it can traverse the chunk of memory and find the object which should be held by the interior pointer. Since this algorithm is very expensive there are limitations what you can do. For instance you cannot have a “ref field” in an object.
87. What is a ref struct?
It is a structure which cannot be moved to the heap. It is always stored on the stack and because of that cannot be accessed by multiple threads. You declare it like this:
1 2 3 4 |
public ref struct Foo { public int Bar; } |
If you try to use Foo
as a field or property of a class, box it or create an array of it – you will get a compilation error.
88. What is an unsafe struct?
If you put an array inside a structure, only the reference to the array is stored. If you want to store whole array inside a structure you need to create so called “fixed size buffer”. It is a fixed array of primitives which you can use in unsafe structures only:
1 2 3 4 |
unsafe struct FixedBufferExample { public fixed int buffer[1024]; // This is a fixed buffer. } |
89. What is a readonly struct?
It is a structure which cannot be modified after creation. It differs from normal structure with readonly fields that you cannot modify this
:
1 2 3 4 |
public readonly struct Foo { public readonly int Bar; } |
90. What is an unsafe type?
This is easy, it is just a class in which you can use unsafe code. Just like an unsafe method.
91. What is a blittable type?
It is a type which has the same binary representation in managed an unmanaged code. Integer, pointers and floating point values are blittable. Oddly enough, bool
is not blittable as it can be stored as 1, 2 or 4-byte value, char
and DateTime
are non-blittable as well. See more here.
92. How is unmanaged class compiled in C++/CLI?
You can create both managed and unmanaged classes and methods, see this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include "stdafx.h" #include < cstdio> using namespace System; ref class Foo { }; class Bar { }; void Baz() { System::Console::WriteLine("In managed function."); } #pragma managed(push, off) void Taz() { printf("In unmanaged function.\n"); } #pragma managed(pop) int main(array<System::String ^> ^args) { Console::WriteLine(L"Hello World"); return 0; } |
When you decompile it, you can find class Foo
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
.class private auto ansi beforefieldinit Foo extends [mscorlib]System.Object { // Methods .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x10c0 // Code size 7 (0x7) .maxstack 1 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method Foo::.ctor } // end of class Foo |
You can also find method main
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
.method assembly static int32 main ( string[] args ) cil managed { // Method begins at RVA 0x10ec // Code size 16 (0x10) .maxstack 1 .locals ( [0] int32 ) IL_0000: ldc.i4.0 IL_0001: stloc.0 IL_0002: ldstr "Hello World" IL_0007: call void [mscorlib]System.Console::WriteLine(string) IL_000c: ldc.i4.0 IL_000d: stloc.0 IL_000e: ldloc.0 IL_000f: ret } // end of method '<Module>'::main |
And method Baz
:
1 2 3 4 5 6 7 8 9 10 11 |
.method assembly static void modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) Baz () cil managed { // Method begins at RVA 0x10d4 // Code size 11 (0xb) .maxstack 1 IL_0000: ldstr "In managed function." IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: ret } // end of method '<Module>'::Baz |
Class Bar
and method Taz
are unmanaged.