First, place a breakpoint on the collectgeneration method of the gcinterface class in the \ CLR \ SRC \ VM \ comutilnative. cpp file of sscli2.0 source code. This macro method mainly implements a call conversion:
/*==============================CollectGeneration===============================**Action: Collects all generations <= args->generation**Returns: void**Arguments: args->generation: The maximum generation to collect**Exceptions: Argument exception if args->generation is < 0 or > GetMaxGeneration();==============================================================================*/FCIMPL1(void, GCInterface::CollectGeneration, INT32 generation){ CONTRACTL { MODE_COOPERATIVE; DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F. THROWS; SO_TOLERANT; } CONTRACTL_END; //We've already checked this in GC.cs, so we'll just assert it here. _ASSERTE(generation >= -1); //We don't need to check the top end because the GC will take care of that. HELPER_METHOD_FRAME_BEGIN_0(); GCHeap::GetGCHeap()->GarbageCollect(generation); if (g_TrapReturningThreads) { GetThread()->PulseGCMode(); } HELPER_METHOD_FRAME_END();}FCIMPLEND
This method provides an interface for calling the internal functions of the sscli virtual execution engine from the BCl. In the sscli implementation version, this call conversion method is called fcall.
In jit-free code, for example, some helper code snippets or stubs, some components that call conversions to coordinate different functions are often used. In this way, a heap with all functions is not displayed in runtime.
Using this very effective call conversion enables hosting of advanced languages, such as C #, to call internal functions in runtime in user code. To use this call, you only need to add the methodimploptions. internalcall attribute to the implemented method.
Fcall will use the ecfunc struct in sscli20 \ CLR \ SRC \ VM \ eCall. cpp to complete the conversion of the C ++ method implemented from the hosting Method to the runtime:
struct ECFunc { UINT_PTR m_dwFlags;#ifndef DACCESS_COMPILE LPVOID m_pImplementation;#else TADDR m_pImplementation;#endif PTR_MethodDesc m_pMD; // for reverse mapping PTR_ECFunc m_pNext; // linked list for hash table LPCUTF8 m_wszMethodName; LPHARDCODEDMETASIG m_wszMethodSig; // Optional field. It is valid only if HasSignature() is set. bool IsEndOfArray() { LEAF_CONTRACT; return !!(m_dwFlags & FCFuncFlag_EndOfArray); } bool HasSignature() { LEAF_CONTRACT; return !!(m_dwFlags & FCFuncFlag_HasSignature); } bool IsUnreferenced(){ LEAF_CONTRACT; return !!(m_dwFlags & FCFuncFlag_Unreferenced); } CorInfoIntrinsics IntrinsicID() { LEAF_CONTRACT; return (CorInfoIntrinsics)((INT8)(m_dwFlags >> 16)); } int DynamicID() { LEAF_CONTRACT; return (int) ((INT8)(m_dwFlags >> 24)); } ECFunc* NextInArray() { LEAF_CONTRACT; return (ECFunc*)((BYTE*)this + (HasSignature() ? sizeof(ECFunc) : offsetof(ECFunc, m_wszMethodSig))); }};
M_wszmethodname indicates the corresponding method in BCl. M_pimplementation indicates the corresponding method in runtime. As you can see, this conversion does not involve any logic such as parameter passing or type check, because the methods called by fcall are fully implemented within runtime.
The methodclassification Enumeration type in the method. cpp file also lists the method categories in other sscli execution engines:
// Used in MethodDescenum MethodClassification{ mcIL = 0, // IL mcFCall = 1, // FCall (also includes tlbimped ctor, Delegate ctor) mcNDirect = 2, // N/Direct mcEEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke) mcArray = 4, // Array ECall mcInstantiated = 5, // Instantiated generic methods, including descriptors // for both shared and unshared code (see InstantiatedMethodDesc) mcDynamic = 7, // for method dewsc with no metadata behind mcCount,};
In the sscli Object Memory layout, because the methoddesc structure is a few different types of aggregates, here we use a three-bit flag to indicate that methoddesc is the type used. In methodclassfication, the method is neither jited nor non-jited. Whether the method has been JIT or not is known only when the method is executed for the first time. At the same time, because all threads in the hosting process need to modify these three bit bits, This identifier is placed on the memory address that can be synchronized by the thread.
Methodclassification is used in methoddesc to identify a method type. In addition, methoddesc has a 16-bit flag (methodclassification) to indicate all attributes of a methoddesc. You can refer to the introduction to methoddesc in the previous chapter.
Note that the following code installs a stack frame in the stack before calling the garbagecollect method in the gcheap class:
//We don't need to check the top end because the GC will take care of that.HELPER_METHOD_FRAME_BEGIN_0();GCHeap::GetGCHeap()->GarbageCollect(generation);if (g_TrapReturningThreads){ GetThread()->PulseGCMode();}HELPER_METHOD_FRAME_END();
In the preceding method, the helper_method_frame_begin_0 () method helper_method_frame_end () is used in pairs to place helpermethodframe stack frames in gcheap. The main function of this stack frame is to allow the JIT helper or fcall information to be added to the stack to facilitate the program's traversal of the stack. The following is the constructor of the frame:
// Lazy initialization of HelperMethodFrame. Need to// call InsureInit to complete initialization// If this is an FCall, the second param is the entry point for the FCALL.// The MethodDesc will be looked up form this (lazily), and this method// will be used in stack reporting, if this is not an FCall pass a 0HelperMethodFrame(void* fCallFtnEntry, struct LazyMachState* ms, unsigned attribs = 0){ WRAPPER_CONTRACT; INDEBUG(memset(&m_Attribs, 0xCC, sizeof(HelperMethodFrame) - offsetof(HelperMethodFrame, m_Attribs));) m_Attribs = attribs; LazyInit(fCallFtnEntry, ms);}
By the way, lazy initialization technology is used here. Lazy Initialization is a method for delaying object initialization, for example, calculating a value, or, if the computing cost of a process is relatively high and is not frequently used, it is initialized during the first use. The implementation method mainly uses a flag to identify whether the process has started. This is also a design model. You can find a detailed description of this technology in Wikipedia:
Http://en.wikipedia.org/wiki/Lazy_initialization
Finally, in the comutilnative. cpp file, many other classes and methods called between Bcl and runtime are implemented, including: