Substring 在BCL和CLR裏面搞了啥

楔子

還是做點事情,不要那麼散漫。

本文以簡單的Substring(int startindex,int Length)函數爲例,來遞進下它在託管和非託管的一些行爲。

以下均爲個人理解,如有疏漏請指正。



定義和實現

它的定義是在System.Runtime.dll裏面

public string Substring(int startIndex, int length)
{
   throw null;
}

它的實現在System.Private.CoreLib.dll裏面

  public string Substring(int startIndex, int length)
  {
    //此處省略一萬字
     return InternalSubString(startIndex, length);
  }

繼續來看下InternalSubString

private string InternalSubString(int startIndex, int length)
{
	string text = string.FastAllocateString(length);
	UIntPtr elementCount = (UIntPtr)text.Length;
	Buffer.Memmove<char>(ref text._firstChar, Unsafe.Add<char>(ref this._firstChar, (IntPtr)((UIntPtr)startIndex)), elementCount);
	return text;
}

FastAllocateString是個FCall函數(也就是微軟提供的在託管裏面調用非託管的一種方式,它的實際實現是在JIT裏面)

	[MethodImpl(MethodImplOptions.InternalCall)]
	internal static extern string FastAllocateString(int length);

Buffer.Memmove是個託管函數,它的作用主要是把FastAllocateString返回的string對象賦值爲startIndex和elementCount中間的字符串。過程是利用了Unsafe.Add(它的定義在System.Runtime.CompilerServices,實現實在CLR裏面)指針偏移來實現,過程比較簡單,不贅述。



FastAllocateString

重點在於這個函數,這個函數進入到了非託管。它進入的方式是通過RyuJit加載這個方法的IL代碼。然後對這個IL代碼進行解析,重構成彙編代碼。

它的非託管原型如下:

#define _DYNAMICALLY_ASSIGNED_FCALLS_BASE() \
    DYNAMICALLY_ASSIGNED_FCALL_IMPL(FastAllocateString,                FramedAllocateString) \

FramedAllocateString原型如下:

HCIMPL1(StringObject*, FramedAllocateString, DWORD stringLength)
{
    FCALL_CONTRACT;

    STRINGREF result = NULL;
    HELPER_METHOD_FRAME_BEGIN_RET_0();    // Set up a frame

    result = AllocateString(stringLength);

    HELPER_METHOD_FRAME_END();
    return((StringObject*) OBJECTREFToObject(result));
}
HCIMPLEND

注意了,FastAllocateString實際上調用的不是FramedAllocateString。因爲在CLR啓動加載的時候,FastAllocateString被替換成了FCall函數形式的調用

ECall::DynamicallyAssignFCallImpl(GetEEFuncEntryPoint(AllocateStringFastMP_InlineGetThread), ECall::FastAllocateString);

DynamicallyAssignFCallImpl原型:

void ECall::DynamicallyAssignFCallImpl(PCODE impl, DWORD index)
{
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_ANY;
    }
    CONTRACTL_END;

    _ASSERTE(index < NUM_DYNAMICALLY_ASSIGNED_FCALL_IMPLEMENTATIONS);
    g_FCDynamicallyAssignedImplementations[index] = impl;
}

可以看到FastAllocateString作爲了索引Index,而他的實現是AllocateStringFastMP_InlineGetThread。

再來看下它的堆棧

>	coreclr.dll!ECall::DynamicallyAssignFCallImpl(unsigned __int64 0x00007ffdeed5df50, unsigned long 0x061b1d50)	C++
 	coreclr.dll!InitJITHelpers1()	C++
 	coreclr.dll!EEStartupHelper()	C++
 	coreclr.dll!`EEStartup'::`9'::__Body::Run(void * 0x0000000000000000)	C++
 	coreclr.dll!EEStartup()	C++
 	coreclr.dll!EnsureEEStarted()	C++
 	coreclr.dll!CorHost2::Start()	C++
 	coreclr.dll!coreclr_initialize(const char * 

很明顯它是在CLR初始化的時候被替代的



何時被調用

最後一個問題,既然FastAllocateString被替代了,那它何時被調用的呢?

在代碼:

private string InternalSubString(int startIndex, int length)
{
	string text = string.FastAllocateString(length);
	UIntPtr elementCount = (UIntPtr)text.Length;
	Buffer.Memmove<char>(ref text._firstChar, Unsafe.Add<char>(ref this._firstChar, (IntPtr)((UIntPtr)startIndex)), elementCount);
	return text;
}

這裏面調用了string.FastAllocateString函數,通過上面推斷,實際上它已經被被替換了。注意了,但是替換之前,還得按照CLR內存模型進行運作調用。當我們調用InternalSubString的時候,裏面調用了FastAllocateString,後者通過PrecodeFixupThunk來進行替換。

這點可以通過彙編驗證:

System_Private_CoreLib!System.String.InternalSubString+0xc:
00007ffd`9a86132c 418bc8          mov     ecx,r8d
0:000> t
System_Private_CoreLib!System.String.InternalSubString+0xf:
00007ffd`9a86132f ff15b39f7e00    call    qword ptr [System_Private_CoreLib+0x9cb2e8 (00007ffd`9b04b2e8)] ds:00007ffd`9b04b2e8={coreclr!AllocateStringFastMP_InlineGetThread (00007ffd`9b20b3a0)}
0:000> t
coreclr!AllocateStringFastMP_InlineGetThread:
00007ffd`9b20b3a0 4c8b0d090d3400  mov     r9,qword ptr [coreclr!g_pStringClass (00007ffd`9b54c0b0)] ds:00007ffd`9b54c0b0=00007ffd3b6ed698
call    qword ptr [System_Private_CoreLib+0x9cb2e8 (00007ffd`9b04b2e8)] ds:00007ffd`9b04b2e8={coreclr!AllocateStringFastMP_InlineGetThread (00007ffd`9b20b3a0)}
就是直接調用了AllocateStringFastMP_InlineGetThread,然後跳轉到後者的地址


AllocateStringFastMP_InlineGetThread

這個函數的作用實際上是申請內存,比如你 new 一個對象的時候,又或者本例,你需要一個新的字符串對象來存儲截取的字符串。
image



作者:江湖評談
版權:本作品採用「署名-非商業性使用-相同方式共享 4.0 國際」許可協議進行許可。
image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章