It shows that the CLR loads a process. In this process, there may be multiple threads. When a thread is created, it is allocated to a 1 MB stack, which is used to pass real parameters to the method and store local variables defined in the method. Stack is built from high memory address to low memory address. In the figure, the thread executes some code and some data already exists on its stack.
The most basic method should contain some "prologue" code, which is responsible for initializing the method before it starts to do its work. In addition, some "epilo-tip" code should be included, which is responsible for cleaning the method after it is completed to return to the caller. Now, it is assumed that the Code executed by the thread needs to call the M1 method. When this method is executed, its "Opening" code allocates memory for the local variable name from the thread stack. Then, M1 calls the M2 method and passes the local variable name as a real parameter. This causes the address in the name local variable to be pushed into the stack. Inside the M2 method, the parameter variable named S is used to identify the stack variable. In addition, a "return address" is pushed into the stack when a method is called. After the called method is completed, it should be returned to this position.
When the M2 method starts execution, its "Opening Remarks" code allocates memory for the local variable length and tally from the thread stack, as shown in. Then, execute the code inside the M2 method. In the end, M2 will arrive at its return statement, which will cause the CPU instruction pointer to be set to the return address in the stack, and the M2 stack frame will be unwind ). Then, M1 will continue to execute the code after the M2 call, And the M1 stack frame will accurately reflect the status required by M1.
Now, let's adjust and discuss it around CLR. Assume there are two class definitions:
internal class Employee
{
public Int32 GetYearEmployed() {...}
public virtual string GenProgressReport() {...}
public static Employee Lookup(string name) {...}
}
internal sealed class Manager : Employee
{
public override string GetProgressReport() {...}
}
Our windows process has been started, CLR has been added to it, the managed heap has been initialized, and a thread has been created (along with its 1 MB stack space ). This thread has executed some code and is now calling the M3 method. The current status is displayed.
When the JIT compiler converts the Il code of m3 to a local CPU command, it will notice all types referenced in M3: employee, int32, manager, and string. In this case, the CLR must ensure that all the assemblies that define these types have been loaded into the appdomain. Then, using the Assembly metadata, CLR extracts information about these types and creates some data structures to represent the types themselves. Displays the data structure of objects for the employee and manager types. Since these threads have executed some code before calling m3, it is assumed that int32 and string objects have been created, so they are not shown in the figure.
Let's discuss these types of objects. All objects on the stack contain two additional members: Type object pointers and synchronized block indexes. When defining a type, you can define static data fields within the type. The bytes that support these static data fields are allocated in the type object itself. Each type object contains a method table. In the method table, each method defined in the type has a corresponding record item.
Now, when the CLR determines that all types of objects required by the method have been created and M2 code has been compiled, the thread can start to execute the local code of M3. When executing the M3 "Opening Remarks" code, the memory must be allocated to the local variable from the thread stack. In addition, as part of the method "Opening Remarks" code, CLR will automatically Initialize all local variables to null or 0.
Then, M3 executes its code to construct a manager object, which creates an instance of the Manager type in the managed heap. As shown in. As you can see, like all objects, the manager object also has a type object pointer and a synchronized block index. This object also contains the bytes required to accommodate all instance data fields defined by the manager type and all instance fields defined by any base class (in this example, employee and object. When a new object is created on the heap, CLR will automatically initialize the internal type object pointer member to reference the type object corresponding to the object (in this example, the manager type object ). In addition, CLR initializes the synchronization block index and sets all instances of the object to null or 0, then the constructor of the type is called (this is a method that may modify some instance data fields ). The new operator returns the memory address of the manager object, which is saved in variable E (E on the thread stack ).
The next line of M3 code calls the static method lookup of employee. When calling a static method, CLR locates the type object corresponding to the type that defines the static method. The CLR then positions the record items that reference the called method in the method table of the type object, then compiles the JIT method (if needed), and finally calls the Code Compiled by JIT. In this example, we assume that the lookup method of employee needs to query a database to find Joe. Internally, the lookup method constructs a new manager object on the stack, initializes it for Joe, and returns the address of the object. This address is saved to the local variable E. As shown in:
Note that e no longer references the first manager object created. The garbage collection mechanism automatically recycles the memory of this object.
The next line of code of M3 calls the non-virtual instance method getyearsemployed of employee. When calling a non-real-world sample method, CLR will find the type object of the type object with the called variable. In this example, variable e is defined as an emplyee. Then, CLR finds the record item that references the called method in the method table of the type object, compiles the method with JIT, and calls the Code Compiled by JIT.
The next line of code of M3 calls genprogressreport of the actual sample method of employee. When calling an actual or virtual method, CLR should do some additional work. First, it searches for the variables used to send the call, and then follows the address to the called object. In this example, variable E points to the Joe manager object. Then, CLR checks the internal type object pointer member of the object, which references the instance type of the object. Then, CLR locates the record item that references the called method in the method table of the type object, compiles the method in JIT, and calls the Code Compiled by JIT. In this example, the genprog-ressreport Implementation of manager is called. The operation result is shown in:
Note that both the employee and manager objects contain type object pointer members. This is because the type object itself is actually an object. When creating a type, the CLR must initialize these members. So what is the specific initialization? When CLR starts running in a process, it will immediately create a special type object for the system. Type type (defined in mscorlib. dll. The employee and manager types are "instances" of this type ". Therefore, their type object pointer members are initialized to reference system. type objects. As shown in:
Of course, the system. Type object itself is also an object, so it also contains a type object pointer member. So what is this pointer pointing? It points to itself, because the system. Type object itself is an "instance" of a type object ".
The GetType method of system. Object returns the address stored in the "type Object Pointer" member of the specified object. In other words, the GetType method returns a pointer to the object type object. In this way, you can determine the actual types of any objects (including type objects) in the system.