一:看下面一些概念

1MethodTable

MethodTable可以說在CLR裡面無處不在,這個東西主要是作為物件的資料型別存在,主要包含了EEClass

模組地址,型別名稱,模組路徑等。

2.EEClass 

EEclass描述了例項物件和型別裡面的函式描述符起始塊地址,起始塊以MethodDescChunk描述。EEclass

成員變數m_pChunks存放了起始塊的地址。

3.MethodDesc

看這個名稱就知道,它是函式描述符。主要包含了函式名稱,函式所述模組,函式是否被jit編譯等屬性。

4.FixUpPreCode

從字面理解修理預程式碼,其實它也是這個功能。就是它描述了託管函式被編譯前和編譯後的變化。編譯前

會呼叫非託管PrecodeFixupThunk函式,編譯後直接跳到編譯的結果。

二:它們是如何組織的

MethodTable會通過CLR生成例項化物件。在這個生成的過程中,會初始化EEClass。然後會給MethodDescChunks

以及MethodDesc分配相應的地址,MethodDesc跟MethodDescChunks地址是連線在一起的,類似於MethodDescChunks

->MethodDes(1)->MehtodDesc(2)->MethodDesc(3)...... 這種形式。下面就是把MethodDescChunks當MethodDesc分配

完成的時候,會遍歷MethodDesc,沒遍歷一個,就會出初始化一個FixUpPreCode結構體。用以描述函式沒被編譯之前

的狀態。他們的關聯方式如下圖所示:

三:CLR是如何被執行出來的

作為一個.Net 程式核心部分,這一塊是一個重點也是一個難點

主要的步驟有以下幾步:

1.CLR會載入當前專案bin/debug/資料夾下面的【目名稱.ConsoleApp15.runtimeconfig.json】的這個檔案,以讀取runtime 的配置

2.CLR 會獲取當前runtime執行時的指標

3.通過runtime執行時指標函式,傳遞進去需要載入的模組,類名稱,以及函式名稱,獲取到需要呼叫的函式指標

4.獲取到函式指標之後,就可以釋放掉上面執行步驟所佔用的記憶體

5.呼叫獲取到的函式指標呼叫函式,比如Main函式,這個是程式入口。

到了這一步不多說了,因為所有的.net程式都是從Main函式開始的。這樣流程就會被C#程式碼所接管。

四:程式碼是如何構造的

由於CLR程式碼長達幾十萬行甚至幾百萬行,所以無法一一展示。

這裡取一部分看看是如何執行的

1.MethodTable的構建過程(MethodTablebuilder.cpp 12163行)

    MethodTableBuilder builder(
NULL,
pClass,
&GetThread()->m_MarshalAlloc,
pamTracker); pMT = builder.BuildMethodTableThrowing(
pAllocator,
pLoaderModule,
pModule,
cl,
pInterfaceBuildInfo,
pLayoutRawFieldInfos,
pParentMethodTable,
&genericsInfo,
parentInst,
(WORD)cInterfaces); END_SO_INTOLERANT_CODE;
RETURN(TypeHandle(pMT));

2.EEclass構建過程(methodtablebuilder.cpp 11957行)

    EEClass * pClass = MethodTableBuilder::CreateClass(
pModule,
cl,
fHasLayout,
fIsDelegate,
fIsEnum,
&genericsInfo,
pAllocator,
pamTracker);

3.MethodDescchunks和MethodDesc的構建過程是在buildmethodtablethrowing函式裡面呼叫    AllocAndInitMethodDescs();,先看看    AllocAndInitMethodDescs();函式(methodtablebuilder.cpp 1666行)

VOID MethodTableBuilder::AllocAndInitMethodDescs()
{
STANDARD_VM_CONTRACT;
if (sizeOfMethodDescs != 0)
{
AllocAndInitMethodDescChunk(startIndex, NumDeclaredMethods() - startIndex, sizeOfMethodDescs);
}
}

4.繼續看 AllocAndInitMethodDescChunk函式(methodtablebuilder.cpp 6836行),很明顯看到了裡面構建了methoddescchunks和methoddesc。

VOID MethodTableBuilder::AllocAndInitMethodDescChunk(COUNT_T startIndex, COUNT_T count, SIZE_T sizeOfMethodDescs)
{
CONTRACTL {
STANDARD_VM_CHECK;
PRECONDITION(sizeOfMethodDescs <= MethodDescChunk::MaxSizeOfMethodDescs);
} CONTRACTL_END; void * pMem = GetMemTracker()->Track(
GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(TADDR) + sizeof(MethodDescChunk) + sizeOfMethodDescs))); // Skip pointer to temporary entrypoints
MethodDescChunk * pChunk = (MethodDescChunk *)((BYTE*)pMem + sizeof(TADDR)); COUNT_T methodDescCount = 0; SIZE_T offset = sizeof(MethodDescChunk); #ifdef _PREFAST_
#pragma warning(push)
#pragma warning(disable:22019) // Suppress PREFast warning about integer underflow
#endif // _PREFAST_
for (COUNT_T i = 0; i < count; i++)
#ifdef _PREFAST_
#pragma warning(pop)
#endif // _PREFAST_ {
bmtMDMethod * pMDMethod = (*bmtMethod)[static_cast<SLOT_INDEX>(startIndex + i)]; MethodDesc * pMD = (MethodDesc *)((BYTE *)pChunk + offset); pMD->SetChunkIndex(pChunk); InitNewMethodDesc(pMDMethod, pMD); #ifdef _PREFAST_
#pragma warning(push)
#pragma warning(disable:22018) // Suppress PREFast warning about integer underflow
#endif // _PREFAST_
offset += pMD->SizeOf();
#ifdef _PREFAST_
#pragma warning(pop)
#endif // _PREFAST_ methodDescCount++; // If we're a value class, we want to create duplicate slots
// and MethodDescs for all methods in the vtable
// section (i.e. not non-virtual instance methods or statics).
// In the name of uniformity it would be much nicer
// if we created _all_ value class BoxedEntryPointStubs at this point.
// However, non-virtual instance methods only require unboxing
// stubs in the rare case that we create a delegate to such a
// method, and thus it would be inefficient to create them on
// loading: after all typical structs will have many non-virtual
// instance methods.
//
// Unboxing stubs for non-virtual instance methods are created
// in code:MethodDesc::FindOrCreateAssociatedMethodDesc. if (NeedsTightlyBoundUnboxingStub(pMDMethod))
{
MethodDesc * pUnboxedMD = (MethodDesc *)((BYTE *)pChunk + offset); //////////////////////////////////
// Initialize the new MethodDesc // <NICE> memcpy operations on data structures like MethodDescs are extremely fragile
// and should not be used. We should go to the effort of having proper constructors
// in the MethodDesc class. </NICE> memcpy(pUnboxedMD, pMD, pMD->SizeOf()); // Reset the chunk index
pUnboxedMD->SetChunkIndex(pChunk); if (bmtGenerics->GetNumGenericArgs() == 0) {
pUnboxedMD->SetHasNonVtableSlot();
} //////////////////////////////////////////////////////////
// Modify the original MethodDesc to be an unboxing stub pMD->SetIsUnboxingStub(); ////////////////////////////////////////////////////////////////////
// Add the new MethodDesc to the non-virtual portion of the vtable if (!bmtVT->AddUnboxedMethod(pMDMethod))
BuildMethodTableThrowException(IDS_CLASSLOAD_TOO_MANY_METHODS); pUnboxedMD->SetSlot(pMDMethod->GetUnboxedSlotIndex());
pMDMethod->SetUnboxedMethodDesc(pUnboxedMD); offset += pUnboxedMD->SizeOf();
methodDescCount++;
}
}
_ASSERTE(offset == sizeof(MethodDescChunk) + sizeOfMethodDescs); pChunk->SetSizeAndCount((ULONG)sizeOfMethodDescs, methodDescCount); GetHalfBakedClass()->AddChunk(pChunk);
}

5.關於構建fixupprecode (methodtablebuilder.cpp 10497行)

   {
for (MethodDescChunk *pChunk = GetHalfBakedClass()->GetChunks(); pChunk != NULL; pChunk = pChunk->GetNextChunk())
{
// Make sure that temporary entrypoints are create for methods. NGEN uses temporary
// entrypoints as surrogate keys for precodes.
pChunk->EnsureTemporaryEntryPointsCreated(GetLoaderAllocator(), GetMemTracker());
}
}