楔子

还是做点事情,不要那么散漫。

本文以简单的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 一个对象的时候,又或者本例,你需要一个新的字符串对象来存储截取的字符串。

作者:江湖评谈

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

Substring 在BCL和CLR里面搞了啥的更多相关文章

  1. [C++/CLI编程宝典][2]什么是C++/CLI语言

    对于什么是C++/CLI,我们首先能够简单的将其名字划分为两部分来理解,第一,C++,我们熟悉的眼下被广泛使用的面向对象的ISO国际标准的高级语言,也称为ISOC++,我们这里以后均称其为ISOC++ ...

  2. C#图解教程第一章 C#和.NET框架

    1.1 在.NET之前 C#发音:see shap 1.1.1 20世纪90年代后期的Windows编程  20世纪90年代后期各语言缺点:   1.纯Win32 API不是面向对象的,而且工作量比M ...

  3. asp.net-基础-20180319

    建立动态web . .net的一部分. HTML:超文本标记语言.WWW浏览器上文档的格式化语言. HTTP:超文本传输协议.WWW浏览器与服务器应用层通信协议. 静态页面:不需要服务器额外代码处理的 ...

  4. [Wiki].NET框架

    .NET框架 建议将.NET Framework 3.0并入本条目或章节.(讨论)  .NET框架 .NET框架的组件堆栈 开发者 Microsoft 初始版本 2002年2月13日,​16年前 稳定 ...

  5. C++ CLI简介(什么是C++ CLI)

    要知道C++/CLI是什么,首先知道什么是CLI. 一.CLI简介 CLI:(Common Language Infrastructure,通用语言框架)提供了一套可执行代码和它所运行需要的虚拟执行环 ...

  6. 关于CLR、CIL、CTS、CLS、CLI、BCL和FCL 的区分与总结

    关于CLR.CIL.CTS.CLS.CLI.BCL和FCL 的区分与总结 如果要想深入学习.NET平台,那么标题中的这些关键字对你来说并不陌生,这些名词构成了.NET庞大的生态系统,为了宏观认识.NE ...

  7. “菜”鸟理解.NET Framework(CLI,CLR,CTS,CLS,BCL,FCL)

    既然要学.NET,就要先认识认识她,我不喜欢大段大段文字的东西,自己通过理解,画个图,来看看.NET的沉鱼落雁,闭月羞花之容. 最下层蓝色部分是.NET Framework的基础,也是所有应用软件的基 ...

  8. 关于CLR、CIL、CTS、CLS、CLI、BCL和FCL

    如果要想深入学习.NET平台,那么标题中的这些关键字对你来说并不陌生,这些名词构成了.NET庞大的生态系统,为了宏观认识.NET平台,学些.NET架构体系,针对一些常用常用名词的理解是很有必要的,未必 ...

  9. “菜”鸟理解.NET Framework(CLI,CLS,CTS,CLR,FCL,BCL)

    既然要学.NET,就要先认识认识她,我不喜欢大段大段文字的东西,自己通过理解,画个图,来看看.NET的沉鱼落雁,闭月羞花之容. 最下层蓝色部分是.NET Framework的基础,也是所有应用软件的基 ...

随机推荐

  1. html table 美化-html如何用css美化表格

    html用css美化表格的方法:首先创建一个HTML示例文件:然后在body中创建table表格:最后通过style标签给表格添加css样式即可. 代码 下面通过示例来看看. /*表格样式*/ tab ...

  2. 【java】学习路径25-ArrayList类,Vector类,LinkedList类的使用和区别,Iterator迭代器的使用

    ArrayList的使用 ArrayList类:可变化长度的数组. 与一般的数组不同的是,其长度不固定,可以添加任意类型的数据. 也可以添加不同类型的数据,但是一般不这么做. ArrayList类位于 ...

  3. RabbitMQ 入门系列:9、扩展内容:死信队列:真不适合当延时队列。

    系列目录 RabbitMQ 入门系列:1.MQ的应用场景的选择与RabbitMQ安装. RabbitMQ 入门系列:2.基础含义:链接.通道.队列.交换机. RabbitMQ 入门系列:3.基础含义: ...

  4. K8S_删除Pod总结

    K8S 不能直接删除Pod,直接删除Pod,会被Deployment重启 删除前,必须先删除对应的Deployment 例子: // 查出Pod [root@k8s-master ~]# kubect ...

  5. 【读书笔记】C#高级编程 第三章 对象和类型

    (一)类和结构 类和结构实际上都是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法. 类和结构的区别:内存中的存储方式.访问方式(类是存储在堆上的引用类型,结构是存储在栈的值类型)和它 ...

  6. 《Markdown常用语法及快捷键》

    Markdown常用语法及快捷键       [```]+空格--代码格式 [---]+空格--水平分割线 [&emsp]+[;]--空格 [shift]+[tab]--清除当前行的格式

  7. day40-网络编程02

    Java网络编程02 4.TCP网络通信编程 基本介绍 基于客户端--服务端的网络通信 底层使用的是TCP/IP协议 应用场景举例:客户端发送数据,服务端接收并显示控制台 基于Scoket的TCP编程 ...

  8. 安装 Helm3 管理 Kubernetes 应用

    文章转载自:http://www.mydlq.club/article/51/ 系统环境: Helm 版本:v3.5.0 Kubernetes 版本:v1.18.2 一.Helm 介绍 Helm 是一 ...

  9. filebeat中增加其他参数用来区分不同的日志来源示例

    filebeat.yml配置文件参数 在source里加入了一个fields叫做"appach: true". filebeat.inputs: - type: log enabl ...

  10. Alertmanager 概念与配置深入介绍

    文章转载自:https://www.cnblogs.com/gered/p/13496950.html 警报一直是整个监控系统中的重要组成部分,Prometheus监控系统中,采集与警报是分离的. 报 ...