.NET跨平台之旅:探秘 dotnet run 如何运行 .NET Core 应用程序
自从用 dotnet run 成功运行第一个 "Hello world" .NET Core 应用程序后,一直有个好奇心:dotnet run 究竟是如何运行一个 .NET Core 应用程序的?
在 从 ASP.NET 5 RC1 升级至 ASP.NET Core 1.0 与 在Linux上以本地机器码运行 ASP.NET Core 站点 之后,这个好奇心被进一步激发,于是“探秘 dotnet run”顺理成章地成为.NET跨平台之旅的下一站。
首先我们了解一下 dotnet 命令是什么东东?dotnet 命令实际就是一个C#写的简单的.NET控制台程序(详见Program.cs),但为什么在不同操作系统平台上安装 dotnet cli 后,dotnet 命令是一个本地可执行文件?功臣依然是前一篇博文中见识过其威力的 .NET Native,dotnet 命令背后的.NET控制台程序被编译为针对不同操作系统的本地机器码,dotnet 命令本身就是 .NET Native 的一个实际应用。
接下来,我们沿着 dotnet 命令的 Program.cs 探寻 dotnet run 运行 .NET Core 应用程序的秘密。
dotnet Program.cs 的C#代码不超过200行,而与我们的探秘之旅最相关的是下面这一段代码:
var builtIns = new Dictionary<string, Func<string[], int>>
{
//...
["run"] = RunCommand.Run,
//...
}; Func<string[], int> builtIn;
if (builtIns.TryGetValue(command, out builtIn))
{
return builtIn(appArgs.ToArray());
}
从上面的代码可以看出,dotnet run 命令实际执行的是 RunCommand 的 Run() 方法,沿着 Run() 方法往前走,从 Start() 方法 来到 RunExecutable() 方法,此处的风景吸引了我们。
在 RunExecutable() 方法中,先执行了 BuildCommand.Run() 方法 —— 对 .NET Core 应用程序进行 build :
var result = Build.BuildCommand.Run(new[]
{
$"--framework",
$"{_context.TargetFramework}",
$"--configuration",
Configuration,
$"{_context.ProjectFile.ProjectDirectory}"
});
如果 build 成功,会在 .NET Core 应用程序的bin文件夹中生成相应的程序集(.dll文件)。如何 build,不是我们这次旅程所关心的,我们关心的是 build 出来的程序集是如何被运行的。
所以略过此处风景,继续向前,发现了下面的代码:
result = Command.Create(outputName, _args)
.ForwardStdOut()
.ForwardStdErr()
.Execute()
.ExitCode;
从上面的代码可以分析出,dotnet run 最终执行的是一个命令行,而这个命令行是由 Command.Create() 根据 outputName 生成的,outputName 就是 BuildCommand 生成的应用程序的程序集名称。显然,秘密一定藏在 Command.Create() 中。
目标 Command.Create() ,跑步前进 。。。在 Command.cs 中看到了 Command.Create() 的庐山真面目:
public static Command Create(
string commandName,
IEnumerable<string> args,
NuGetFramework framework = null,
string configuration = Constants.DefaultConfiguration)
{
var commandSpec = CommandResolver.TryResolveCommandSpec(commandName,
args,
framework,
configuration: configuration); if (commandSpec == null)
{
throw new CommandUnknownException(commandName);
} var command = new Command(commandSpec); return command;
}
发现 CommandResolver,快步迈入 CommandResolver.TryResolveCommandSpec() ,看看其中又是怎样的风景:
public static CommandSpec TryResolveCommandSpec(
string commandName,
IEnumerable<string> args,
NuGetFramework framework = null,
string configuration=Constants.DefaultConfiguration,
string outputPath=null)
{
var commandResolverArgs = new CommandResolverArguments
{
CommandName = commandName,
CommandArguments = args,
Framework = framework,
ProjectDirectory = Directory.GetCurrentDirectory(),
Configuration = configuration,
OutputPath = outputPath
}; var defaultCommandResolver = DefaultCommandResolverPolicy.Create(); return defaultCommandResolver.Resolve(commandResolverArgs);
}
发现 DefaultCommandResolverPolicy ,一个箭步,置身其 Create() 方法中:
public static CompositeCommandResolver Create()
{
var environment = new EnvironmentProvider();
var packagedCommandSpecFactory = new PackagedCommandSpecFactory(); var platformCommandSpecFactory = default(IPlatformCommandSpecFactory);
if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows)
{
platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory();
}
else
{
platformCommandSpecFactory = new GenericPlatformCommandSpecFactory();
} return CreateDefaultCommandResolver(environment, packagedCommandSpecFactory, platformCommandSpecFactory);
}
出现两道风景 —— packagedCommandSpecFactory 与 platformCommandSpecFactory,它们都被作为参数传给了 CreateDefaultCommandResolver() 方法。
一心不可二用,先看其中一道风景 —— packagedCommandSpecFactory ,急不可待地奔向 CreateDefaultCommandResolver() 方法 。
public static CompositeCommandResolver CreateDefaultCommandResolver(
IEnvironmentProvider environment,
IPackagedCommandSpecFactory packagedCommandSpecFactory,
IPlatformCommandSpecFactory platformCommandSpecFactory)
{
var compositeCommandResolver = new CompositeCommandResolver();
//..
compositeCommandResolver.AddCommandResolver(
new ProjectToolsCommandResolver(packagedCommandSpecFactory));
//..
return compositeCommandResolver;
}
packagedCommandSpecFactory 将我们引向新的风景 —— ProjectToolsCommandResolver 。飞奔过去之后,立即被 Resolve() 方法吸引(在之前的 DefaultCommandResolverPolicy.Create() 执行之后,执行 defaultCommandResolver.Resolve(commandResolverArgs) 时,该方法被调用)。
这里的风景十八弯。在 ProjectToolsCommandResolver 中七绕八绕,从 ResolveFromProjectTools() -> ResolveCommandSpecFromAllToolLibraries() -> ResolveCommandSpecFromToolLibrary() 。。。又回到了 PackagedCommandSpecFactory ,进入 CreateCommandSpecFromLibrary() 方法。
在 PackagedCommandSpecFactory 中继续转悠,在从 CreateCommandSpecWrappingWithCorehostfDll() 到 CreatePackageCommandSpecUsingCorehost() 时,发现一个新东东从天而降 —— corehost :
private CommandSpec CreatePackageCommandSpecUsingCorehost(
string commandPath,
IEnumerable<string> commandArguments,
string depsFilePath,
CommandResolutionStrategy commandResolutionStrategy)
{
var corehost = CoreHost.HostExePath; var arguments = new List<string>();
arguments.Add(commandPath); if (depsFilePath != null)
{
arguments.Add($"--depsfile:{depsFilePath}");
} arguments.AddRange(commandArguments); return CreateCommandSpec(corehost, arguments, commandResolutionStrategy);
}
这里的 corehost 变量是干嘛的?心中产生了一个大大的问号。
遥望 corehost 的身后,发现 CreateCommandSpec() 方法(corehost 是它的一个参数),一路狂奔过去:
private CommandSpec CreateCommandSpec(
string commandPath,
IEnumerable<string> commandArguments,
CommandResolutionStrategy commandResolutionStrategy)
{
var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(commandArguments); return new CommandSpec(commandPath, escapedArgs, commandResolutionStrategy);
}
原来 corehost 是作为 commandPath 的值,也就是说 Command.Create() 创建的 dotnet run 所对应的命令行是以 corehost 开头的,秘密一定就在 corehost 的前方不远处。
corehost 的值来自 CoreHost.HostExePath ,HostExePath 的值来自 Constants.HostExecutableName ,HostExecutableName 的值是:
public static readonly string HostExecutableName = "corehost" + ExeSuffix;
corehost 命令!原来 dotnet run 命令最终执行的 corehost 命令,corehost 才是背后真正的主角,.NET Core 应用程序是由它运行的。
去 dotnet cli 的安装目录一看,果然有一个 corehost 可执行文件。
-rwxr-xr-x root root Mar : /usr/share/dotnet-nightly/bin/corehost
既然 corehost 是主角,那么不通过 dotnet run ,直接用 corehost 应该也可以运行 .NET Core 程序,我们来试一试。
进入示例站点 about.cnblogs.com 的 build 输出文件夹:
cd /git/AboutUs/bin/Debug/netstandardapp1.3/ubuntu.14.04-x64
然后直接用 corehost 命令运行程序集:
/usr/share/dotnet-nightly/bin/corehost AboutUs.dll
运行成功!事实证明 corehost 是运行 .NET Core 程序的主角。
dbug: Microsoft.AspNetCore.Hosting.Internal.WebHost[3]
Hosting starting
dbug: Microsoft.AspNetCore.Hosting.Internal.WebHost[4]
Hosting started
Hosting environment: Production
Application base path: /git/AboutUs/bin/Debug/netstandardapp1.3/ubuntu.14.04-x64
Now listening on: http://*:8001
Application started. Press Ctrl+C to shut down.
去 dotnet cli 的源代码中看一下 corehost 的实现代码,是用 C++ 写的,这是 dotnet cli 中唯一用 C++ 实现的部分,它也不得不用 C++ ,因为它有一个重要职责 —— 加载 coreclr ,再次证实 corehost 是主角。
探秘 dotnet run , 踏破铁鞋,走过千山万水,终于找到了你 —— corehost,终于满足了那颗不安分的好奇心。
.NET跨平台之旅:探秘 dotnet run 如何运行 .NET Core 应用程序的更多相关文章
- ASP.NET Core 中文文档 第二章 指南(8) 使用 dotnet watch 开发 ASP.NET Core 应用程序
原文:Developing ASP.NET Core applications using dotnet watch 作者:Victor Hurdugaci 翻译:谢炀(Kiler) 校对:刘怡(Al ...
- 使用 dotnet watch 开发 ASP.NET Core 应用程序
使用 dotnet watch 开发 ASP.NET Core 应用程序 原文:Developing ASP.NET Core applications using dotnet watch作者:Vi ...
- .NET跨平台之旅:将示例站点升级至 .NET Core 1.1 Preview 1
今天微软发布了 .NET Core 1.1 Preview 1(详见 Announcing .NET Core 1.1 Preview 1 ),紧跟 .NET Core 前进的步伐,我们将示例站点 h ...
- DotNet Run 命令介绍
前言 本篇主要介绍 asp.net core 中,使用 dotnet tools 运行 dotnet run 之后的系统执行过程. 如果你觉得对你有帮助的话,不妨点个[推荐]. 目录 dotnet r ...
- dotnet tools 运行 dotnet run
dotnet tools 运行 dotnet run dotnet run 命令介绍 前言 本篇主要介绍 asp.net core 中,使用 dotnet tools 运行 dotnet run 之后 ...
- .NET跨平台之旅:corehost 是如何加载 coreclr 的
在前一篇博文中,在好奇心的驱使下,探秘了 dotnet run ,发现了神秘的 corehost —— 运行 .NET Core 应用程序的幕后英雄.有时神秘就是一种诱惑,神秘的 corehost ...
- dotnet run是如何启动asp.net core站点的
在曾经的 asp.net 5 过渡时期,运行 asp.net 5 站点的命令是dnx web:在如今即将到来的 asp.net core 时代,运行 asp.net core 站点的命令是dotnet ...
- 【dotnet跨平台】"dotnet restore"和"dotnet run"都做了些什么?
[dotnet跨平台]"dotnet restore"和"dotnet run"都做了些什么? 前言: 关于dotnet跨平台的相关内容.能够參考:跨平台.NE ...
- .NET跨平台之旅:将示例站点从 ASP.NET 5 RC1 升级至 ASP.NET Core 1.0
终于将“.NET跨平台之旅”的示例站点 about.cnblogs.com 从 ASP.NET 5 RC1 升级至 ASP.NET Core 1.0 ,经历了不少周折,在这篇博文中记录一下. 从 AS ...
随机推荐
- JAVA NIO学习笔记1 - 架构简介
最近项目中遇到不少NIO相关知识,之前对这块接触得较少,算是我的一个盲区,打算花点时间学习,简单做一点个人学习总结. 简介 NIO(New IO)是JDK1.4以后推出的全新IO API,相比传统IO ...
- 在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查
Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),.它当前支持M ...
- 静态代理和利用反射形成的动态代理(JDK动态代理)
代理模式 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 静态代理 1.新建 ...
- Spring boot: Request method 'DELETE' not supported, Request method 'PUT' not supported, Request method 'POST' not supported
GET,POST,PUT,DELETE, Spring都支持,不要怀疑Spring, 一定是前端发送的rest 请求和后端的响应不匹配, 查找原因以及解决办法, 很简单 用chrome打开F12控制台 ...
- Golang接口(interface)三个特性(译文)
The Laws of Reflection 原文地址 第一次翻译文章,请各路人士多多指教! 类型和接口 因为映射建设在类型的基础之上,首先我们对类型进行全新的介绍. go是一个静态性语言,每个变量都 ...
- PHP 适配器模式
适配器模式(Adapter)模式:将一个类的接口,转换成客户期望的另一个类的接口.适配器让原本接口不兼容的类可以合作无间. [适配器模式中主要角色]目标(Target)角色:定义客户端使用的与 ...
- Css3新特性应用之视觉效果
一.单侧阴影 box-shadow属性的应用,格式:h-shadow v-shadow blur spread color inset属性取值介绍 h-sahdow:水平阴影的位置,允许负值 v-sh ...
- Android核心组件 Activity组件
1.Activity简介 四大组件之一的Activity组件,在应用中一个Activity可以用来表示一个界面,中文意思也可以理解为"活动",即一个活动开始,代表Activity组 ...
- Intellij idea 和android studio 代码给混淆
Intellij idea 和android studio 代码给混淆 一.指令说明-optimizationpasses 5 # 指定代码的压缩级别 -dontusemixedcaseclassna ...
- Android WebView 302斗争之旅
一.背景 越来越多的业务接入,项目内多多少少会出现几个H5页面,只是单纯的提供WebView容器接入H5页面根本满足不了需求,他们需要登录态,需要制定协议控制Native的导航栏,或者需要JsBrid ...